diff --git a/app/javascript/dashboard/components/widgets/conversation/linear/IssuesList.vue b/app/javascript/dashboard/components/widgets/conversation/linear/IssuesList.vue
new file mode 100644
index 000000000..160394142
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/conversation/linear/IssuesList.vue
@@ -0,0 +1,132 @@
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/linear/LinearIssueItem.vue b/app/javascript/dashboard/components/widgets/conversation/linear/LinearIssueItem.vue
new file mode 100644
index 000000000..e9d1ca500
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/conversation/linear/LinearIssueItem.vue
@@ -0,0 +1,111 @@
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/linear/LinearSetupCTA.vue b/app/javascript/dashboard/components/widgets/conversation/linear/LinearSetupCTA.vue
new file mode 100644
index 000000000..f7394a092
--- /dev/null
+++ b/app/javascript/dashboard/components/widgets/conversation/linear/LinearSetupCTA.vue
@@ -0,0 +1,54 @@
+
+
+
diff --git a/app/javascript/dashboard/components/widgets/conversation/linear/index.vue b/app/javascript/dashboard/components/widgets/conversation/linear/index.vue
deleted file mode 100644
index f7c12fcdf..000000000
--- a/app/javascript/dashboard/components/widgets/conversation/linear/index.vue
+++ /dev/null
@@ -1,161 +0,0 @@
-
-
-
diff --git a/app/javascript/dashboard/composables/useUISettings.js b/app/javascript/dashboard/composables/useUISettings.js
index 29fe878c7..964c615ad 100644
--- a/app/javascript/dashboard/composables/useUISettings.js
+++ b/app/javascript/dashboard/composables/useUISettings.js
@@ -9,6 +9,7 @@ export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = Object.freeze([
{ name: 'contact_notes' },
{ name: 'previous_conversation' },
{ name: 'conversation_participants' },
+ { name: 'linear_issues' },
{ name: 'shopify_orders' },
]);
diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json
index fdb7fc07a..c047f17ad 100644
--- a/app/javascript/dashboard/i18n/locale/en/conversation.json
+++ b/app/javascript/dashboard/i18n/locale/en/conversation.json
@@ -308,6 +308,7 @@
"CONTACT_ATTRIBUTES": "Contact Attributes",
"PREVIOUS_CONVERSATION": "Previous Conversations",
"MACROS": "Macros",
+ "LINEAR_ISSUES": "Linked Linear Issues",
"SHOPIFY_ORDERS": "Shopify Orders"
},
"SHOPIFY": {
diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json
index cfa46867c..071c95604 100644
--- a/app/javascript/dashboard/i18n/locale/en/integrations.json
+++ b/app/javascript/dashboard/i18n/locale/en/integrations.json
@@ -315,11 +315,18 @@
"SUCCESS": "Issue unlinked successfully",
"ERROR": "There was an error unlinking the issue, please try again"
},
+ "NO_LINKED_ISSUES": "No linked issues found",
"DELETE": {
"TITLE": "Are you sure you want to delete the integration?",
"MESSAGE": "Are you sure you want to delete the integration?",
"CONFIRM": "Yes, delete",
"CANCEL": "Cancel"
+ },
+ "CTA": {
+ "TITLE": "Connect to Linear",
+ "AGENT_DESCRIPTION": "Linear workspace is not connected. Request your administrator to connect a workspace to use this integration.",
+ "DESCRIPTION": "Linear workspace is not connected. Click the button below to connect your workspace to use this integration.",
+ "BUTTON_TEXT": "Connect Linear workspace"
}
}
},
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue
index 212d9219b..d7f99802c 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue
@@ -19,6 +19,9 @@ import Draggable from 'vuedraggable';
import MacrosList from './Macros/List.vue';
import ShopifyOrdersList from 'dashboard/components/widgets/conversation/ShopifyOrdersList.vue';
import SidebarActionsHeader from 'dashboard/components-next/SidebarActionsHeader.vue';
+import LinearIssuesList from 'dashboard/components/widgets/conversation/linear/IssuesList.vue';
+import LinearSetupCTA from 'dashboard/components/widgets/conversation/linear/LinearSetupCTA.vue';
+import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const props = defineProps({
conversationId: {
@@ -40,6 +43,13 @@ const {
const dragging = ref(false);
const conversationSidebarItems = ref([]);
+
+const currentAccountId = useMapGetter('getCurrentAccountId');
+
+const isFeatureEnabledonAccount = useMapGetter(
+ 'accounts/isFeatureEnabledonAccount'
+);
+
const shopifyIntegration = useFunctionGetter(
'integrations/getIntegration',
'shopify'
@@ -49,6 +59,20 @@ const isShopifyFeatureEnabled = computed(
() => shopifyIntegration.value.enabled
);
+const linearIntegration = useFunctionGetter(
+ 'integrations/getIntegration',
+ 'linear'
+);
+
+const isLinearIntegrationEnabled = computed(
+ () => linearIntegration.value?.enabled || false
+);
+
+const isLinearFeatureEnabled = isFeatureEnabledonAccount.value(
+ currentAccountId.value,
+ FEATURE_FLAGS.LINEAR
+);
+
const store = useStore();
const currentChat = useMapGetter('getSelectedChat');
const conversationId = computed(() => props.conversationId);
@@ -103,6 +127,8 @@ onMounted(() => {
conversationSidebarItems.value = conversationSidebarItemsOrder.value;
getContactDetails();
store.dispatch('attributes/get', 0);
+ // Load integrations to ensure linear integration state is available
+ store.dispatch('integrations/get', 'linear');
});
@@ -113,7 +139,7 @@ onMounted(() => {
@close="closeContactPanel"
/>
+
{
@end="onDragEnd"
>
-
-
-
toggleSidebarUIState('is_conv_actions_open', value)
- "
- >
-
-
-
-
-
- toggleSidebarUIState('is_conv_participants_open', value)
- "
- >
-
-
-
-
-
toggleSidebarUIState('is_conv_details_open', value)
- "
- >
-
-
-
-
-
- toggleSidebarUIState('is_contact_attributes_open', value)
- "
- >
-
-
-
-
-
toggleSidebarUIState('is_previous_conv_open', value)
- "
- >
-
-
-
-
- toggleSidebarUIState('is_macro_open', value)"
- >
-
-
-
-
+
toggleSidebarUIState('is_conv_actions_open', value)
"
>
- toggleSidebarUIState('is_shopify_orders_open', value)
+
+
+
+
+
+ toggleSidebarUIState('is_conv_participants_open', value)
+ "
+ >
+
+
+
+
+
toggleSidebarUIState('is_conv_details_open', value)
+ "
+ >
+
+
+
+
+
+ toggleSidebarUIState('is_contact_attributes_open', value)
+ "
+ >
+
-
-
-
-
-
toggleSidebarUIState('is_contact_notes_open', value)
- "
- >
-
-
-
+ />
+
+
+
+
toggleSidebarUIState('is_previous_conv_open', value)
+ "
+ >
+
+
+
+
+ toggleSidebarUIState('is_macro_open', value)"
+ >
+
+
+
+
+
toggleSidebarUIState('is_linear_issues_open', value)
+ "
+ >
+
+
+
+
+
+
toggleSidebarUIState('is_shopify_orders_open', value)
+ "
+ >
+
+
+
+
+
toggleSidebarUIState('is_contact_notes_open', value)
+ "
+ >
+
+
From c0d9533b3a3a5bfb07584ff23cc68290ff939021 Mon Sep 17 00:00:00 2001
From: Uchkun Rakhimov
Date: Wed, 11 Jun 2025 02:31:28 +0500
Subject: [PATCH 04/15] feat: add support for telegram circular video messages
(#11504)
This PR adds support for handling Telegram's video_note messages (circular video messages), which are commonly used for short voice-style video replies in messaging.
Fixes: #11503
Co-authored-by: Sojan Jose
---
.../telegram/incoming_message_service.rb | 23 ++++++++++++---
.../telegram/incoming_message_service_spec.rb | 29 +++++++++++++++++++
2 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb
index e337a03f6..3a186676e 100644
--- a/app/services/telegram/incoming_message_service.rb
+++ b/app/services/telegram/incoming_message_service.rb
@@ -94,13 +94,25 @@ class Telegram::IncomingMessageService
end
def file_content_type
- return :image if params[:message][:photo].present? || params.dig(:message, :sticker, :thumb).present?
- return :audio if params[:message][:voice].present? || params[:message][:audio].present?
- return :video if params[:message][:video].present?
+ return :image if image_message?
+ return :audio if audio_message?
+ return :video if video_message?
file_type(params[:message][:document][:mime_type])
end
+ def image_message?
+ params[:message][:photo].present? || params.dig(:message, :sticker, :thumb).present?
+ end
+
+ def audio_message?
+ params[:message][:voice].present? || params[:message][:audio].present?
+ end
+
+ def video_message?
+ params[:message][:video].present? || params[:message][:video_note].present?
+ end
+
def attach_files
return unless file
@@ -174,6 +186,9 @@ class Telegram::IncomingMessageService
end
def visual_media_params
- params[:message][:photo].presence&.last || params.dig(:message, :sticker, :thumb).presence || params[:message][:video].presence
+ params[:message][:photo].presence&.last ||
+ params.dig(:message, :sticker, :thumb).presence ||
+ params[:message][:video].presence ||
+ params[:message][:video_note].presence
end
end
diff --git a/spec/services/telegram/incoming_message_service_spec.rb b/spec/services/telegram/incoming_message_service_spec.rb
index 0702baa32..78a8e9d7c 100644
--- a/spec/services/telegram/incoming_message_service_spec.rb
+++ b/spec/services/telegram/incoming_message_service_spec.rb
@@ -179,6 +179,35 @@ describe Telegram::IncomingMessageService do
end
end
+ context 'when valid video_note messages params' do
+ it 'creates appropriate conversations, message and contacts' do
+ allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.mov')
+ params = {
+ 'update_id' => 2_342_342_343_242,
+ 'message' => {
+ 'video_note' => {
+ 'duration' => 3,
+ 'length' => 240,
+ 'thumb' => {
+ 'file_id' => 'AAMCBQADGQEAA4ZhXd78Xz6_c6gCzbdIkgGiXJcwwwACqwMAAp3x8Fbhf3EWamgCWAEAB20AAyEE',
+ 'file_unique_id' => 'AQADqwMAAp3x8FZy',
+ 'file_size' => 11_462,
+ 'width' => 240,
+ 'height' => 240
+ },
+ 'file_id' => 'DQACAgUAAxkBAAIBY2FdJlhf8PC2E3IalXSvXWO5m8GBAALJAwACwqHgVhb0truM0uhwIQQ',
+ 'file_unique_id' => 'AgADyQMAAsKh4FY',
+ 'file_size' => 132_446
+ }
+ }.merge(message_params)
+ }.with_indifferent_access
+ described_class.new(inbox: telegram_channel.inbox, params: params).perform
+ expect(telegram_channel.inbox.conversations.count).not_to eq(0)
+ expect(Contact.all.first.name).to eq('Sojan Jose')
+ expect(telegram_channel.inbox.messages.first.attachments.first.file_type).to eq('video')
+ end
+ end
+
context 'when valid voice attachment params' do
it 'creates appropriate conversations, message and contacts' do
allow(telegram_channel.inbox.channel).to receive(:get_telegram_file_path).and_return('https://chatwoot-assets.local/sample.ogg')
From 5bf5a0e0d0b288b56b6579516d60b9f27627f1b2 Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 10:12:57 +0530
Subject: [PATCH 05/15] chore: design fixes
---
.../settings/inbox/channels/Whatsapp.vue | 2 +-
.../inbox/channels/WhatsappEmbeddedSignup.vue | 22 +++++++++++--------
2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue
index 01adfdf43..3dc9e860c 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/Whatsapp.vue
@@ -89,7 +89,7 @@ const shouldShowCloudWhatsapp = provider => {
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue
index efe7ad1cf..5383046bd 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/channels/WhatsappEmbeddedSignup.vue
@@ -61,15 +61,19 @@ onBeforeUnmount(() => {
-
- {{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUBMIT_BUTTON') }}
-
+
+
+ {{ $t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUBMIT_BUTTON') }}
+
+
+
Date: Wed, 11 Jun 2025 14:35:46 +0530
Subject: [PATCH 06/15] feat: label reports overview (#11194)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Muhsin Keloth
---
.../v2/reports/label_summary_builder.rb | 101 ++++++
.../v2/accounts/summary_reports_controller.rb | 6 +-
app/helpers/api/v2/accounts/reports_helper.rb | 10 +-
.../dashboard/api/summaryReports.js | 10 +
.../components-next/sidebar/Sidebar.vue | 2 +-
.../dashboard/i18n/locale/en/report.json | 2 +
.../settings/reports/LabelReportsIndex.vue | 35 ++
.../settings/reports/LabelReportsShow.vue | 31 ++
.../reports/components/SummaryReports.vue | 5 +-
.../settings/reports/reports.routes.js | 18 +
.../dashboard/store/modules/labels.js | 3 +
.../modules/specs/summaryReports.spec.js | 40 +++
.../dashboard/store/modules/summaryReports.js | 17 +
config/routes.rb | 1 +
lib/seeders/reports/conversation_creator.rb | 105 ++++++
lib/seeders/reports/message_creator.rb | 141 ++++++++
lib/seeders/reports/report_data_seeder.rb | 234 +++++++++++++
lib/tasks/seed_reports_data.rake | 24 ++
.../v2/reports/label_summary_builder_spec.rb | 317 ++++++++++++++++++
19 files changed, 1095 insertions(+), 7 deletions(-)
create mode 100644 app/builders/v2/reports/label_summary_builder.rb
create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsIndex.vue
create mode 100644 app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsShow.vue
create mode 100644 lib/seeders/reports/conversation_creator.rb
create mode 100644 lib/seeders/reports/message_creator.rb
create mode 100644 lib/seeders/reports/report_data_seeder.rb
create mode 100644 lib/tasks/seed_reports_data.rake
create mode 100644 spec/builders/v2/reports/label_summary_builder_spec.rb
diff --git a/app/builders/v2/reports/label_summary_builder.rb b/app/builders/v2/reports/label_summary_builder.rb
new file mode 100644
index 000000000..abc68b26b
--- /dev/null
+++ b/app/builders/v2/reports/label_summary_builder.rb
@@ -0,0 +1,101 @@
+class V2::Reports::LabelSummaryBuilder < V2::Reports::BaseSummaryBuilder
+ attr_reader :account, :params
+
+ # rubocop:disable Lint/MissingSuper
+ # the parent class has no initialize
+ def initialize(account:, params:)
+ @account = account
+ @params = params
+
+ timezone_offset = (params[:timezone_offset] || 0).to_f
+ @timezone = ActiveSupport::TimeZone[timezone_offset]&.name
+ end
+ # rubocop:enable Lint/MissingSuper
+
+ def build
+ labels = account.labels.to_a
+ return [] if labels.empty?
+
+ report_data = collect_report_data
+ labels.map { |label| build_label_report(label, report_data) }
+ end
+
+ private
+
+ def collect_report_data
+ conversation_filter = build_conversation_filter
+ use_business_hours = use_business_hours?
+
+ {
+ conversation_counts: fetch_conversation_counts(conversation_filter),
+ resolved_counts: fetch_resolved_counts(conversation_filter),
+ resolution_metrics: fetch_metrics(conversation_filter, 'conversation_resolved', use_business_hours),
+ first_response_metrics: fetch_metrics(conversation_filter, 'first_response', use_business_hours),
+ reply_metrics: fetch_metrics(conversation_filter, 'reply', use_business_hours)
+ }
+ end
+
+ def build_label_report(label, report_data)
+ {
+ id: label.id,
+ name: label.title,
+ conversations_count: report_data[:conversation_counts][label.title] || 0,
+ avg_resolution_time: report_data[:resolution_metrics][label.title] || 0,
+ avg_first_response_time: report_data[:first_response_metrics][label.title] || 0,
+ avg_reply_time: report_data[:reply_metrics][label.title] || 0,
+ resolved_conversations_count: report_data[:resolved_counts][label.title] || 0
+ }
+ end
+
+ def use_business_hours?
+ ActiveModel::Type::Boolean.new.cast(params[:business_hours])
+ end
+
+ def build_conversation_filter
+ conversation_filter = { account_id: account.id }
+ conversation_filter[:created_at] = range if range.present?
+
+ conversation_filter
+ end
+
+ def fetch_conversation_counts(conversation_filter)
+ fetch_counts(conversation_filter)
+ end
+
+ def fetch_resolved_counts(conversation_filter)
+ fetch_counts(conversation_filter.merge(status: :resolved))
+ end
+
+ def fetch_counts(conversation_filter)
+ ActsAsTaggableOn::Tagging
+ .joins('INNER JOIN conversations ON taggings.taggable_id = conversations.id')
+ .joins('INNER JOIN tags ON taggings.tag_id = tags.id')
+ .where(
+ taggable_type: 'Conversation',
+ context: 'labels',
+ conversations: conversation_filter
+ )
+ .select('tags.name, COUNT(taggings.*) AS count')
+ .group('tags.name')
+ .each_with_object({}) { |record, hash| hash[record.name] = record.count }
+ end
+
+ def fetch_metrics(conversation_filter, event_name, use_business_hours)
+ ReportingEvent
+ .joins('INNER JOIN conversations ON reporting_events.conversation_id = conversations.id')
+ .joins('INNER JOIN taggings ON taggings.taggable_id = conversations.id')
+ .joins('INNER JOIN tags ON taggings.tag_id = tags.id')
+ .where(
+ conversations: conversation_filter,
+ name: event_name,
+ taggings: { taggable_type: 'Conversation', context: 'labels' }
+ )
+ .group('tags.name')
+ .order('tags.name')
+ .select(
+ 'tags.name',
+ use_business_hours ? 'AVG(reporting_events.value_in_business_hours) as avg_value' : 'AVG(reporting_events.value) as avg_value'
+ )
+ .each_with_object({}) { |record, hash| hash[record.name] = record.avg_value.to_f }
+ end
+end
diff --git a/app/controllers/api/v2/accounts/summary_reports_controller.rb b/app/controllers/api/v2/accounts/summary_reports_controller.rb
index 989952cfd..f31a53c7e 100644
--- a/app/controllers/api/v2/accounts/summary_reports_controller.rb
+++ b/app/controllers/api/v2/accounts/summary_reports_controller.rb
@@ -1,6 +1,6 @@
class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseController
before_action :check_authorization
- before_action :prepare_builder_params, only: [:agent, :team, :inbox]
+ before_action :prepare_builder_params, only: [:agent, :team, :inbox, :label]
def agent
render_report_with(V2::Reports::AgentSummaryBuilder)
@@ -14,6 +14,10 @@ class Api::V2::Accounts::SummaryReportsController < Api::V1::Accounts::BaseContr
render_report_with(V2::Reports::InboxSummaryBuilder)
end
+ def label
+ render_report_with(V2::Reports::LabelSummaryBuilder)
+ end
+
private
def check_authorization
diff --git a/app/helpers/api/v2/accounts/reports_helper.rb b/app/helpers/api/v2/accounts/reports_helper.rb
index 22c51b6ef..23694d08d 100644
--- a/app/helpers/api/v2/accounts/reports_helper.rb
+++ b/app/helpers/api/v2/accounts/reports_helper.rb
@@ -36,9 +36,13 @@ module Api::V2::Accounts::ReportsHelper
end
def generate_labels_report
- Current.account.labels.map do |label|
- label_report = report_builder({ type: :label, id: label.id }).short_summary
- [label.title] + generate_readable_report_metrics(label_report)
+ reports = V2::Reports::LabelSummaryBuilder.new(
+ account: Current.account,
+ params: build_params({})
+ ).build
+
+ reports.map do |report|
+ [report[:name]] + generate_readable_report_metrics(report)
end
end
diff --git a/app/javascript/dashboard/api/summaryReports.js b/app/javascript/dashboard/api/summaryReports.js
index f772ef86f..fad26bf6f 100644
--- a/app/javascript/dashboard/api/summaryReports.js
+++ b/app/javascript/dashboard/api/summaryReports.js
@@ -35,6 +35,16 @@ class SummaryReportsAPI extends ApiClient {
},
});
}
+
+ getLabelReports({ since, until, businessHours } = {}) {
+ return axios.get(`${this.url}/label`, {
+ params: {
+ since,
+ until,
+ business_hours: businessHours,
+ },
+ });
+ }
}
export default new SummaryReportsAPI();
diff --git a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
index 5d7aac3c3..171f4a4d8 100644
--- a/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
+++ b/app/javascript/dashboard/components-next/sidebar/Sidebar.vue
@@ -87,7 +87,7 @@ const newReportRoutes = () => [
{
name: 'Reports Label',
label: t('SIDEBAR.REPORTS_LABEL'),
- to: accountScopedRoute('label_reports'),
+ to: accountScopedRoute('label_reports_index'),
},
{
name: 'Reports Inbox',
diff --git a/app/javascript/dashboard/i18n/locale/en/report.json b/app/javascript/dashboard/i18n/locale/en/report.json
index 294ca2e7b..7c42fdfba 100644
--- a/app/javascript/dashboard/i18n/locale/en/report.json
+++ b/app/javascript/dashboard/i18n/locale/en/report.json
@@ -193,6 +193,7 @@
},
"LABEL_REPORTS": {
"HEADER": "Labels Overview",
+ "DESCRIPTION": "Track label performance with key metrics including conversations, response times, resolution times, and resolved cases. Click a label name for detailed insights.",
"LOADING_CHART": "Loading chart data...",
"NO_ENOUGH_DATA": "We've not received enough data points to generate report, Please try again later.",
"DOWNLOAD_LABEL_REPORTS": "Download label reports",
@@ -559,6 +560,7 @@
"INBOX": "Inbox",
"AGENT": "Agent",
"TEAM": "Team",
+ "LABEL": "Label",
"AVG_RESOLUTION_TIME": "Avg. Resolution Time",
"AVG_FIRST_RESPONSE_TIME": "Avg. First Response Time",
"AVG_REPLY_TIME": "Avg. Customer Waiting Time",
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsIndex.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsIndex.vue
new file mode 100644
index 000000000..956b30974
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsIndex.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsShow.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsShow.vue
new file mode 100644
index 000000000..678028410
--- /dev/null
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/LabelReportsShow.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue
index 4c0c4fe64..7e0b9be8f 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/components/SummaryReports.vue
@@ -108,7 +108,8 @@ const tableData = computed(() =>
} = rowMetrics;
return {
id: row.id,
- name: row.name,
+ // we fallback on title, label for instance does not have a name property
+ name: row.name ?? row.title,
type: props.type,
conversationsCount: renderCount(conversationsCount),
avgFirstResponseTime: renderAvgTime(avgFirstResponseTime),
@@ -177,7 +178,7 @@ defineExpose({ downloadReports });
diff --git a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
index 43855240c..af01f6d74 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
+++ b/app/javascript/dashboard/routes/dashboard/settings/reports/reports.routes.js
@@ -7,10 +7,12 @@ import Index from './Index.vue';
import AgentReportsIndex from './AgentReportsIndex.vue';
import InboxReportsIndex from './InboxReportsIndex.vue';
import TeamReportsIndex from './TeamReportsIndex.vue';
+import LabelReportsIndex from './LabelReportsIndex.vue';
import AgentReportsShow from './AgentReportsShow.vue';
import InboxReportsShow from './InboxReportsShow.vue';
import TeamReportsShow from './TeamReportsShow.vue';
+import LabelReportsShow from './LabelReportsShow.vue';
import AgentReports from './AgentReports.vue';
import InboxReports from './InboxReports.vue';
@@ -104,6 +106,22 @@ const revisedReportRoutes = [
},
component: TeamReportsShow,
},
+ {
+ path: 'labels_overview',
+ name: 'label_reports_index',
+ meta: {
+ permissions: ['administrator', 'report_manage'],
+ },
+ component: LabelReportsIndex,
+ },
+ {
+ path: 'labels/:id',
+ name: 'label_reports_show',
+ meta: {
+ permissions: ['administrator', 'report_manage'],
+ },
+ component: LabelReportsShow,
+ },
];
export default {
diff --git a/app/javascript/dashboard/store/modules/labels.js b/app/javascript/dashboard/store/modules/labels.js
index 34f63f2a1..662c2973e 100644
--- a/app/javascript/dashboard/store/modules/labels.js
+++ b/app/javascript/dashboard/store/modules/labels.js
@@ -26,6 +26,9 @@ export const getters = {
.filter(record => record.show_on_sidebar)
.sort((a, b) => a.title.localeCompare(b.title));
},
+ getLabelById: _state => id => {
+ return _state.records.find(record => record.id === Number(id));
+ },
};
export const actions = {
diff --git a/app/javascript/dashboard/store/modules/specs/summaryReports.spec.js b/app/javascript/dashboard/store/modules/specs/summaryReports.spec.js
index 043987136..c1a199856 100644
--- a/app/javascript/dashboard/store/modules/specs/summaryReports.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/summaryReports.spec.js
@@ -7,6 +7,7 @@ vi.mock('dashboard/api/summaryReports', () => ({
getInboxReports: vi.fn(),
getAgentReports: vi.fn(),
getTeamReports: vi.fn(),
+ getLabelReports: vi.fn(),
},
}));
@@ -25,10 +26,12 @@ describe('Summary Reports Store', () => {
inboxSummaryReports: [],
agentSummaryReports: [],
teamSummaryReports: [],
+ labelSummaryReports: [],
uiFlags: {
isFetchingInboxSummaryReports: false,
isFetchingAgentSummaryReports: false,
isFetchingTeamSummaryReports: false,
+ isFetchingLabelSummaryReports: false,
},
});
});
@@ -39,6 +42,7 @@ describe('Summary Reports Store', () => {
inboxSummaryReports: [{ id: 1 }],
agentSummaryReports: [{ id: 2 }],
teamSummaryReports: [{ id: 3 }],
+ labelSummaryReports: [{ id: 4 }],
uiFlags: { isFetchingInboxSummaryReports: true },
};
@@ -54,6 +58,10 @@ describe('Summary Reports Store', () => {
expect(store.getters.getTeamSummaryReports(state)).toEqual([{ id: 3 }]);
});
+ it('should return label summary reports', () => {
+ expect(store.getters.getLabelSummaryReports(state)).toEqual([{ id: 4 }]);
+ });
+
it('should return UI flags', () => {
expect(store.getters.getUIFlags(state)).toEqual({
isFetchingInboxSummaryReports: true,
@@ -86,6 +94,14 @@ describe('Summary Reports Store', () => {
expect(state.teamSummaryReports).toEqual(data);
});
+ it('should set label summary report', () => {
+ const state = { ...initialState };
+ const data = [{ id: 4 }];
+
+ store.mutations.setLabelSummaryReport(state, data);
+ expect(state.labelSummaryReports).toEqual(data);
+ });
+
it('should merge UI flags with existing flags', () => {
const state = {
uiFlags: { flag1: true, flag2: false },
@@ -185,5 +201,29 @@ describe('Summary Reports Store', () => {
});
});
});
+
+ describe('fetchLabelSummaryReports', () => {
+ it('should fetch label reports successfully', async () => {
+ const params = { labelId: 789 };
+ const mockResponse = {
+ data: [{ label_id: 789, label_name: 'Test Label' }],
+ };
+
+ SummaryReportsAPI.getLabelReports.mockResolvedValue(mockResponse);
+
+ await store.actions.fetchLabelSummaryReports({ commit }, params);
+
+ expect(commit).toHaveBeenCalledWith('setUIFlags', {
+ isFetchingLabelSummaryReports: true,
+ });
+ expect(SummaryReportsAPI.getLabelReports).toHaveBeenCalledWith(params);
+ expect(commit).toHaveBeenCalledWith('setLabelSummaryReport', [
+ { labelId: 789, labelName: 'Test Label' },
+ ]);
+ expect(commit).toHaveBeenCalledWith('setUIFlags', {
+ isFetchingLabelSummaryReports: false,
+ });
+ });
+ });
});
});
diff --git a/app/javascript/dashboard/store/modules/summaryReports.js b/app/javascript/dashboard/store/modules/summaryReports.js
index 15df7589b..d95c0df30 100644
--- a/app/javascript/dashboard/store/modules/summaryReports.js
+++ b/app/javascript/dashboard/store/modules/summaryReports.js
@@ -17,6 +17,11 @@ const typeMap = {
apiMethod: 'getTeamReports',
mutationKey: 'setTeamSummaryReport',
},
+ label: {
+ flagKey: 'isFetchingLabelSummaryReports',
+ apiMethod: 'getLabelReports',
+ mutationKey: 'setLabelSummaryReport',
+ },
};
async function fetchSummaryReports(type, params, { commit }) {
@@ -38,10 +43,12 @@ export const initialState = {
inboxSummaryReports: [],
agentSummaryReports: [],
teamSummaryReports: [],
+ labelSummaryReports: [],
uiFlags: {
isFetchingInboxSummaryReports: false,
isFetchingAgentSummaryReports: false,
isFetchingTeamSummaryReports: false,
+ isFetchingLabelSummaryReports: false,
},
};
@@ -55,6 +62,9 @@ export const getters = {
getTeamSummaryReports(state) {
return state.teamSummaryReports;
},
+ getLabelSummaryReports(state) {
+ return state.labelSummaryReports;
+ },
getUIFlags(state) {
return state.uiFlags;
},
@@ -72,6 +82,10 @@ export const actions = {
fetchTeamSummaryReports({ commit }, params) {
return fetchSummaryReports('team', params, { commit });
},
+
+ fetchLabelSummaryReports({ commit }, params) {
+ return fetchSummaryReports('label', params, { commit });
+ },
};
export const mutations = {
@@ -84,6 +98,9 @@ export const mutations = {
setTeamSummaryReport(state, data) {
state.teamSummaryReports = data;
},
+ setLabelSummaryReport(state, data) {
+ state.labelSummaryReports = data;
+ },
setUIFlags(state, uiFlag) {
state.uiFlags = { ...state.uiFlags, ...uiFlag };
},
diff --git a/config/routes.rb b/config/routes.rb
index 12ce70d77..1ed080457 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -344,6 +344,7 @@ Rails.application.routes.draw do
get :agent
get :team
get :inbox
+ get :label
end
end
resources :reports, only: [:index] do
diff --git a/lib/seeders/reports/conversation_creator.rb b/lib/seeders/reports/conversation_creator.rb
new file mode 100644
index 000000000..b6259de7d
--- /dev/null
+++ b/lib/seeders/reports/conversation_creator.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'faker'
+require 'active_support/testing/time_helpers'
+
+class Seeders::Reports::ConversationCreator
+ include ActiveSupport::Testing::TimeHelpers
+
+ def initialize(account:, resources:)
+ @account = account
+ @contacts = resources[:contacts]
+ @inboxes = resources[:inboxes]
+ @teams = resources[:teams]
+ @labels = resources[:labels]
+ @agents = resources[:agents]
+ @priorities = [nil, 'urgent', 'high', 'medium', 'low']
+ end
+
+ def create_conversation(created_at:)
+ conversation = nil
+
+ ActiveRecord::Base.transaction do
+ travel_to(created_at) do
+ conversation = build_conversation
+ conversation.save!
+
+ add_labels_to_conversation(conversation)
+ create_messages_for_conversation(conversation)
+ resolve_conversation_if_needed(conversation)
+ end
+
+ travel_back
+ end
+
+ conversation
+ end
+
+ private
+
+ def build_conversation
+ contact = @contacts.sample
+ inbox = @inboxes.sample
+
+ contact_inbox = find_or_create_contact_inbox(contact, inbox)
+ assignee = select_assignee(inbox)
+ team = select_team
+ priority = @priorities.sample
+
+ contact_inbox.conversations.new(
+ account: @account,
+ inbox: inbox,
+ contact: contact,
+ assignee: assignee,
+ team: team,
+ priority: priority
+ )
+ end
+
+ def find_or_create_contact_inbox(contact, inbox)
+ inbox.contact_inboxes.find_or_create_by!(
+ contact: contact,
+ source_id: SecureRandom.hex
+ )
+ end
+
+ def select_assignee(inbox)
+ rand(10) < 8 ? inbox.members.sample : nil
+ end
+
+ def select_team
+ rand(10) < 7 ? @teams.sample : nil
+ end
+
+ def add_labels_to_conversation(conversation)
+ labels_to_add = @labels.sample(rand(5..20))
+ conversation.update_labels(labels_to_add.map(&:title))
+ end
+
+ def create_messages_for_conversation(conversation)
+ message_creator = Seeders::Reports::MessageCreator.new(
+ account: @account,
+ agents: @agents,
+ conversation: conversation
+ )
+ message_creator.create_messages
+ end
+
+ def resolve_conversation_if_needed(conversation)
+ return unless rand < 0.7
+
+ resolution_delay = rand((30.minutes)..(24.hours))
+ travel(resolution_delay)
+ conversation.update!(status: :resolved)
+
+ trigger_conversation_resolved_event(conversation)
+ end
+
+ def trigger_conversation_resolved_event(conversation)
+ event_data = { conversation: conversation }
+
+ ReportingEventListener.instance.conversation_resolved(
+ Events::Base.new('conversation_resolved', Time.current, event_data)
+ )
+ end
+end
diff --git a/lib/seeders/reports/message_creator.rb b/lib/seeders/reports/message_creator.rb
new file mode 100644
index 000000000..fc10716d9
--- /dev/null
+++ b/lib/seeders/reports/message_creator.rb
@@ -0,0 +1,141 @@
+# frozen_string_literal: true
+
+require 'faker'
+require 'active_support/testing/time_helpers'
+
+class Seeders::Reports::MessageCreator
+ include ActiveSupport::Testing::TimeHelpers
+
+ MESSAGES_PER_CONVERSATION = 5
+
+ def initialize(account:, agents:, conversation:)
+ @account = account
+ @agents = agents
+ @conversation = conversation
+ end
+
+ def create_messages
+ message_count = rand(MESSAGES_PER_CONVERSATION..MESSAGES_PER_CONVERSATION + 5)
+ first_agent_reply = true
+
+ message_count.times do |i|
+ message = create_single_message(i)
+ first_agent_reply = handle_reply_tracking(message, i, first_agent_reply)
+ end
+ end
+
+ def create_single_message(index)
+ is_incoming = index.even?
+ add_realistic_delay(index, is_incoming) if index.positive?
+ create_message(is_incoming)
+ end
+
+ def handle_reply_tracking(message, index, first_agent_reply)
+ return first_agent_reply if index.even? # Skip incoming messages
+
+ handle_agent_reply_events(message, first_agent_reply)
+ false # No longer first reply after any agent message
+ end
+
+ private
+
+ def add_realistic_delay(_message_index, is_incoming)
+ delay = calculate_message_delay(is_incoming)
+ travel(delay)
+ end
+
+ def calculate_message_delay(is_incoming)
+ if is_incoming
+ # Customer response time: 1 minute to 4 hours
+ rand((1.minute)..(4.hours))
+ elsif business_hours_active?(Time.current)
+ # Agent response time varies by business hours
+ rand((30.seconds)..(30.minutes))
+ else
+ rand((1.hour)..(8.hours))
+ end
+ end
+
+ def create_message(is_incoming)
+ if is_incoming
+ create_incoming_message
+ else
+ create_outgoing_message
+ end
+ end
+
+ def create_incoming_message
+ @conversation.messages.create!(
+ account: @account,
+ inbox: @conversation.inbox,
+ message_type: :incoming,
+ content: generate_message_content,
+ sender: @conversation.contact
+ )
+ end
+
+ def create_outgoing_message
+ sender = @conversation.assignee || @agents.sample
+
+ @conversation.messages.create!(
+ account: @account,
+ inbox: @conversation.inbox,
+ message_type: :outgoing,
+ content: generate_message_content,
+ sender: sender
+ )
+ end
+
+ def generate_message_content
+ Faker::Lorem.paragraph(sentence_count: rand(1..5))
+ end
+
+ def handle_agent_reply_events(message, is_first_reply)
+ if is_first_reply
+ trigger_first_reply_event(message)
+ else
+ trigger_reply_event(message)
+ end
+ end
+
+ def business_hours_active?(time)
+ weekday = time.wday
+ hour = time.hour
+ weekday.between?(1, 5) && hour.between?(9, 17)
+ end
+
+ def trigger_first_reply_event(message)
+ event_data = {
+ message: message,
+ conversation: message.conversation
+ }
+
+ ReportingEventListener.instance.first_reply_created(
+ Events::Base.new('first_reply_created', Time.current, event_data)
+ )
+ end
+
+ def trigger_reply_event(message)
+ waiting_since = calculate_waiting_since(message)
+
+ event_data = {
+ message: message,
+ conversation: message.conversation,
+ waiting_since: waiting_since
+ }
+
+ ReportingEventListener.instance.reply_created(
+ Events::Base.new('reply_created', Time.current, event_data)
+ )
+ end
+
+ def calculate_waiting_since(message)
+ last_customer_message = message.conversation.messages
+ .where(message_type: :incoming)
+ .where('created_at < ?', message.created_at)
+ .order(:created_at)
+ .last
+
+ last_customer_message&.created_at || message.conversation.created_at
+ end
+end
diff --git a/lib/seeders/reports/report_data_seeder.rb b/lib/seeders/reports/report_data_seeder.rb
new file mode 100644
index 000000000..909818b72
--- /dev/null
+++ b/lib/seeders/reports/report_data_seeder.rb
@@ -0,0 +1,234 @@
+# frozen_string_literal: true
+
+# Reports Data Seeder
+#
+# Generates realistic test data for performance testing of reports and analytics.
+# Creates conversations, messages, contacts, agents, teams, and labels with proper
+# reporting events (first response times, resolution times, etc.) using time travel
+# to generate historical data with realistic timestamps.
+#
+# Usage:
+# ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data
+#
+# This will create:
+# - 1000 conversations with realistic message exchanges
+# - 100 contacts with realistic profiles
+# - 20 agents assigned to teams and inboxes
+# - 5 teams with realistic distribution
+# - 30 labels with random assignments
+# - 3 inboxes with agent assignments
+# - Realistic reporting events with historical timestamps
+#
+# Note: This seeder clears existing data for the account before seeding.
+
+require 'faker'
+require_relative 'conversation_creator'
+require_relative 'message_creator'
+
+# rubocop:disable Rails/Output
+class Seeders::Reports::ReportDataSeeder
+ include ActiveSupport::Testing::TimeHelpers
+
+ TOTAL_CONVERSATIONS = 1000
+ TOTAL_CONTACTS = 100
+ TOTAL_AGENTS = 20
+ TOTAL_TEAMS = 5
+ TOTAL_LABELS = 30
+ TOTAL_INBOXES = 3
+ MESSAGES_PER_CONVERSATION = 5
+ START_DATE = 3.months.ago # rubocop:disable Rails/RelativeDateConstant
+ END_DATE = Time.current
+
+ def initialize(account:)
+ raise 'Account Seeding is not allowed.' unless ENV.fetch('ENABLE_ACCOUNT_SEEDING', !Rails.env.production?)
+
+ @account = account
+ @teams = []
+ @agents = []
+ @labels = []
+ @inboxes = []
+ @contacts = []
+ end
+
+ def perform!
+ puts "Starting reports data seeding for account: #{@account.name}"
+
+ # Clear existing data
+ clear_existing_data
+
+ create_teams
+ create_agents
+ create_labels
+ create_inboxes
+ create_contacts
+
+ create_conversations
+
+ puts "Completed reports data seeding for account: #{@account.name}"
+ end
+
+ private
+
+ def clear_existing_data
+ puts "Clearing existing data for account: #{@account.id}"
+ @account.teams.destroy_all
+ @account.conversations.destroy_all
+ @account.labels.destroy_all
+ @account.inboxes.destroy_all
+ @account.contacts.destroy_all
+ @account.agents.destroy_all
+ @account.reporting_events.destroy_all
+ end
+
+ def create_teams
+ TOTAL_TEAMS.times do |i|
+ team = @account.teams.create!(
+ name: "#{Faker::Company.industry} Team #{i + 1}"
+ )
+ @teams << team
+ print "\rCreating teams: #{i + 1}/#{TOTAL_TEAMS}"
+ end
+
+ print "\n"
+ end
+
+ def create_agents
+ TOTAL_AGENTS.times do |i|
+ user = create_single_agent(i)
+ assign_agent_to_teams(user)
+ @agents << user
+ print "\rCreating agents: #{i + 1}/#{TOTAL_AGENTS}"
+ end
+
+ print "\n"
+ end
+
+ def create_single_agent(index)
+ random_suffix = SecureRandom.hex(4)
+ user = User.create!(
+ name: Faker::Name.name,
+ email: "agent_#{index + 1}_#{random_suffix}@#{@account.domain || 'example.com'}",
+ password: 'Password1!.',
+ confirmed_at: Time.current
+ )
+ user.skip_confirmation!
+ user.save!
+
+ AccountUser.create!(
+ account_id: @account.id,
+ user_id: user.id,
+ role: :agent
+ )
+
+ user
+ end
+
+ def assign_agent_to_teams(user)
+ teams_to_assign = @teams.sample(rand(1..3))
+ teams_to_assign.each do |team|
+ TeamMember.create!(
+ team_id: team.id,
+ user_id: user.id
+ )
+ end
+ end
+
+ def create_labels
+ TOTAL_LABELS.times do |i|
+ label = @account.labels.create!(
+ title: "Label-#{i + 1}-#{Faker::Lorem.word}",
+ description: Faker::Company.catch_phrase,
+ color: Faker::Color.hex_color
+ )
+ @labels << label
+ print "\rCreating labels: #{i + 1}/#{TOTAL_LABELS}"
+ end
+
+ print "\n"
+ end
+
+ def create_inboxes
+ TOTAL_INBOXES.times do |_i|
+ inbox = create_single_inbox
+ assign_agents_to_inbox(inbox)
+ @inboxes << inbox
+ print "\rCreating inboxes: #{@inboxes.size}/#{TOTAL_INBOXES}"
+ end
+
+ print "\n"
+ end
+
+ def create_single_inbox
+ channel = Channel::WebWidget.create!(
+ website_url: "https://#{Faker::Internet.domain_name}",
+ account_id: @account.id
+ )
+
+ @account.inboxes.create!(
+ name: "#{Faker::Company.name} Website",
+ channel: channel
+ )
+ end
+
+ def assign_agents_to_inbox(inbox)
+ agents_to_assign = if @inboxes.empty?
+ # First inbox gets all agents to ensure coverage
+ @agents
+ else
+ # Subsequent inboxes get random selection with some overlap
+ min_agents = [@agents.size / TOTAL_INBOXES, 10].max
+ max_agents = [(@agents.size * 0.8).to_i, 50].min
+ @agents.sample(rand(min_agents..max_agents))
+ end
+
+ agents_to_assign.each do |agent|
+ InboxMember.create!(inbox: inbox, user: agent)
+ end
+ end
+
+ def create_contacts
+ TOTAL_CONTACTS.times do |i|
+ contact = @account.contacts.create!(
+ name: Faker::Name.name,
+ email: Faker::Internet.email,
+ phone_number: Faker::PhoneNumber.cell_phone_in_e164,
+ identifier: SecureRandom.uuid,
+ additional_attributes: {
+ company: Faker::Company.name,
+ city: Faker::Address.city,
+ country: Faker::Address.country,
+ customer_since: Faker::Date.between(from: 2.years.ago, to: Time.zone.today)
+ }
+ )
+ @contacts << contact
+
+ print "\rCreating contacts: #{i + 1}/#{TOTAL_CONTACTS}"
+ end
+
+ print "\n"
+ end
+
+ def create_conversations
+ conversation_creator = Seeders::Reports::ConversationCreator.new(
+ account: @account,
+ resources: {
+ contacts: @contacts,
+ inboxes: @inboxes,
+ teams: @teams,
+ labels: @labels,
+ agents: @agents
+ }
+ )
+
+ TOTAL_CONVERSATIONS.times do |i|
+ created_at = Faker::Time.between(from: START_DATE, to: END_DATE)
+ conversation_creator.create_conversation(created_at: created_at)
+
+ completion_percentage = ((i + 1).to_f / TOTAL_CONVERSATIONS * 100).round
+ print "\rCreating conversations: #{i + 1}/#{TOTAL_CONVERSATIONS} (#{completion_percentage}%)"
+ end
+
+ print "\n"
+ end
+end
+# rubocop:enable Rails/Output
diff --git a/lib/tasks/seed_reports_data.rake b/lib/tasks/seed_reports_data.rake
new file mode 100644
index 000000000..5c80f1e51
--- /dev/null
+++ b/lib/tasks/seed_reports_data.rake
@@ -0,0 +1,24 @@
+namespace :db do
+ namespace :seed do
+ desc 'Seed test data for reports with conversations, contacts, agents, teams, and realistic reporting events'
+ task reports_data: :environment do
+ if ENV['ACCOUNT_ID'].blank?
+ puts 'Please provide an ACCOUNT_ID environment variable'
+ puts 'Usage: ACCOUNT_ID=1 ENABLE_ACCOUNT_SEEDING=true bundle exec rake db:seed:reports_data'
+ exit 1
+ end
+
+ ENV['ENABLE_ACCOUNT_SEEDING'] = 'true' if ENV['ENABLE_ACCOUNT_SEEDING'].blank?
+
+ account_id = ENV.fetch('ACCOUNT_ID', nil)
+ account = Account.find(account_id)
+
+ puts "Starting reports data seeding for account: #{account.name} (ID: #{account.id})"
+
+ seeder = Seeders::Reports::ReportDataSeeder.new(account: account)
+ seeder.perform!
+
+ puts "Finished seeding reports data for account: #{account.name}"
+ end
+ end
+end
diff --git a/spec/builders/v2/reports/label_summary_builder_spec.rb b/spec/builders/v2/reports/label_summary_builder_spec.rb
new file mode 100644
index 000000000..7a5a589dd
--- /dev/null
+++ b/spec/builders/v2/reports/label_summary_builder_spec.rb
@@ -0,0 +1,317 @@
+require 'rails_helper'
+
+RSpec.describe V2::Reports::LabelSummaryBuilder do
+ include ActiveJob::TestHelper
+
+ let_it_be(:account) { create(:account) }
+ let_it_be(:label_1) { create(:label, title: 'label_1', account: account) }
+ let_it_be(:label_2) { create(:label, title: 'label_2', account: account) }
+ let_it_be(:label_3) { create(:label, title: 'label_3', account: account) }
+
+ let(:params) do
+ {
+ business_hours: business_hours,
+ since: (Time.zone.today - 3.days).to_time.to_i.to_s,
+ until: Time.zone.today.end_of_day.to_time.to_i.to_s,
+ timezone_offset: 0
+ }
+ end
+ let(:builder) { described_class.new(account: account, params: params) }
+
+ describe '#initialize' do
+ let(:business_hours) { false }
+
+ it 'sets account and params' do
+ expect(builder.account).to eq(account)
+ expect(builder.params).to eq(params)
+ end
+
+ it 'sets timezone from timezone_offset' do
+ builder_with_offset = described_class.new(account: account, params: { timezone_offset: -8 })
+ expect(builder_with_offset.instance_variable_get(:@timezone)).to eq('Pacific Time (US & Canada)')
+ end
+
+ it 'defaults timezone when timezone_offset is not provided' do
+ builder_without_offset = described_class.new(account: account, params: {})
+ expect(builder_without_offset.instance_variable_get(:@timezone)).not_to be_nil
+ end
+ end
+
+ describe '#build' do
+ context 'when there are no labels' do
+ let(:business_hours) { false }
+ let(:empty_account) { create(:account) }
+ let(:empty_builder) { described_class.new(account: empty_account, params: params) }
+
+ it 'returns empty array' do
+ expect(empty_builder.build).to eq([])
+ end
+ end
+
+ context 'when there are labels but no conversations' do
+ let(:business_hours) { false }
+
+ it 'returns zero values for all labels' do
+ report = builder.build
+
+ expect(report.length).to eq(3)
+
+ bug_report = report.find { |r| r[:name] == 'label_1' }
+ feature_request = report.find { |r| r[:name] == 'label_2' }
+ customer_support = report.find { |r| r[:name] == 'label_3' }
+
+ [
+ [bug_report, label_1, 'label_1'],
+ [feature_request, label_2, 'label_2'],
+ [customer_support, label_3, 'label_3']
+ ].each do |report_data, label, label_name|
+ expect(report_data).to include(
+ id: label.id,
+ name: label_name,
+ conversations_count: 0,
+ avg_resolution_time: 0,
+ avg_first_response_time: 0,
+ avg_reply_time: 0,
+ resolved_conversations_count: 0
+ )
+ end
+ end
+ end
+
+ context 'when there are labeled conversations with metrics' do
+ before do
+ travel_to(Time.zone.today) do
+ user = create(:user, account: account)
+ inbox = create(:inbox, account: account)
+ create(:inbox_member, user: user, inbox: inbox)
+
+ gravatar_url = 'https://www.gravatar.com'
+ stub_request(:get, /#{gravatar_url}.*/).to_return(status: 404)
+
+ perform_enqueued_jobs do
+ # Create conversations with label_1
+ 3.times do
+ conversation = create(:conversation, account: account,
+ inbox: inbox, assignee: user,
+ created_at: Time.zone.today)
+ create_list(:message, 2, message_type: 'outgoing',
+ account: account, inbox: inbox,
+ conversation: conversation,
+ created_at: Time.zone.today + 1.hour)
+ create_list(:message, 1, message_type: 'incoming',
+ account: account, inbox: inbox,
+ conversation: conversation,
+ created_at: Time.zone.today + 2.hours)
+ conversation.update_labels('label_1')
+ conversation.label_list
+ conversation.save!
+ end
+
+ # Create conversations with label_2
+ 2.times do
+ conversation = create(:conversation, account: account,
+ inbox: inbox, assignee: user,
+ created_at: Time.zone.today)
+ create_list(:message, 1, message_type: 'outgoing',
+ account: account, inbox: inbox,
+ conversation: conversation,
+ created_at: Time.zone.today + 1.hour)
+ conversation.update_labels('label_2')
+ conversation.label_list
+ conversation.save!
+ end
+
+ # Resolve some conversations
+ conversations_to_resolve = account.conversations.first(2)
+ conversations_to_resolve.each(&:toggle_status)
+
+ # Create some reporting events
+ account.conversations.reload.each_with_index do |conv, idx|
+ # First response times
+ create(:reporting_event,
+ account: account,
+ conversation: conv,
+ name: 'first_response',
+ value: (30 + (idx * 10)) * 60,
+ value_in_business_hours: (20 + (idx * 5)) * 60,
+ created_at: Time.zone.today)
+
+ # Reply times
+ create(:reporting_event,
+ account: account,
+ conversation: conv,
+ name: 'reply',
+ value: (15 + (idx * 5)) * 60,
+ value_in_business_hours: (10 + (idx * 3)) * 60,
+ created_at: Time.zone.today)
+
+ # Resolution times for resolved conversations
+ next unless conv.resolved?
+
+ create(:reporting_event,
+ account: account,
+ conversation: conv,
+ name: 'conversation_resolved',
+ value: (60 + (idx * 30)) * 60,
+ value_in_business_hours: (45 + (idx * 20)) * 60,
+ created_at: Time.zone.today)
+ end
+ end
+ end
+ end
+
+ context 'when business hours is disabled' do
+ let(:business_hours) { false }
+
+ it 'returns correct label stats using regular values' do
+ report = builder.build
+
+ expect(report.length).to eq(3)
+
+ label_1_report = report.find { |r| r[:name] == 'label_1' }
+ label_2_report = report.find { |r| r[:name] == 'label_2' }
+ label_3_report = report.find { |r| r[:name] == 'label_3' }
+
+ expect(label_1_report).to include(
+ conversations_count: 3,
+ avg_first_response_time: be > 0,
+ avg_reply_time: be > 0
+ )
+
+ expect(label_2_report).to include(
+ conversations_count: 2,
+ avg_first_response_time: be > 0,
+ avg_reply_time: be > 0
+ )
+
+ expect(label_3_report).to include(
+ conversations_count: 0,
+ avg_first_response_time: 0,
+ avg_reply_time: 0
+ )
+ end
+ end
+
+ context 'when business hours is enabled' do
+ let(:business_hours) { true }
+
+ it 'returns correct label stats using business hours values' do
+ report = builder.build
+
+ expect(report.length).to eq(3)
+
+ label_1_report = report.find { |r| r[:name] == 'label_1' }
+ label_2_report = report.find { |r| r[:name] == 'label_2' }
+
+ expect(label_1_report[:conversations_count]).to eq(3)
+ expect(label_1_report[:avg_first_response_time]).to be > 0
+ expect(label_1_report[:avg_reply_time]).to be > 0
+
+ expect(label_2_report[:conversations_count]).to eq(2)
+ expect(label_2_report[:avg_first_response_time]).to be > 0
+ expect(label_2_report[:avg_reply_time]).to be > 0
+ end
+ end
+ end
+
+ context 'when filtering by date range' do
+ let(:business_hours) { false }
+
+ before do
+ travel_to(Time.zone.today) do
+ user = create(:user, account: account)
+ inbox = create(:inbox, account: account)
+ create(:inbox_member, user: user, inbox: inbox)
+
+ gravatar_url = 'https://www.gravatar.com'
+ stub_request(:get, /#{gravatar_url}.*/).to_return(status: 404)
+
+ perform_enqueued_jobs do
+ # Conversation within range
+ conversation_in_range = create(:conversation, account: account,
+ inbox: inbox, assignee: user,
+ created_at: 2.days.ago)
+ conversation_in_range.update_labels('label_1')
+ conversation_in_range.label_list
+ conversation_in_range.save!
+
+ create(:reporting_event,
+ account: account,
+ conversation: conversation_in_range,
+ name: 'first_response',
+ value: 1800,
+ created_at: 2.days.ago)
+
+ # Conversation outside range (too old)
+ conversation_out_of_range = create(:conversation, account: account,
+ inbox: inbox, assignee: user,
+ created_at: 1.week.ago)
+ conversation_out_of_range.update_labels('label_1')
+ conversation_out_of_range.label_list
+ conversation_out_of_range.save!
+
+ create(:reporting_event,
+ account: account,
+ conversation: conversation_out_of_range,
+ name: 'first_response',
+ value: 3600,
+ created_at: 1.week.ago)
+ end
+ end
+ end
+
+ it 'only includes conversations within the date range' do
+ report = builder.build
+
+ expect(report.length).to eq(3)
+
+ label_1_report = report.find { |r| r[:name] == 'label_1' }
+ expect(label_1_report).not_to be_nil
+ expect(label_1_report[:conversations_count]).to eq(1)
+ expect(label_1_report[:avg_first_response_time]).to eq(1800.0)
+ end
+ end
+
+ context 'with business hours parameter' do
+ let(:business_hours) { 'true' }
+
+ before do
+ travel_to(Time.zone.today) do
+ user = create(:user, account: account)
+ inbox = create(:inbox, account: account)
+ create(:inbox_member, user: user, inbox: inbox)
+
+ gravatar_url = 'https://www.gravatar.com'
+ stub_request(:get, /#{gravatar_url}.*/).to_return(status: 404)
+
+ perform_enqueued_jobs do
+ conversation = create(:conversation, account: account,
+ inbox: inbox, assignee: user,
+ created_at: Time.zone.today)
+ conversation.update_labels('label_1')
+ conversation.label_list
+ conversation.save!
+
+ create(:reporting_event,
+ account: account,
+ conversation: conversation,
+ name: 'first_response',
+ value: 3600,
+ value_in_business_hours: 1800,
+ created_at: Time.zone.today)
+ end
+ end
+ end
+
+ it 'properly casts string "true" to boolean and uses business hours values' do
+ report = builder.build
+
+ expect(report.length).to eq(3)
+
+ label_1_report = report.find { |r| r[:name] == 'label_1' }
+ expect(label_1_report).not_to be_nil
+ expect(label_1_report[:avg_first_response_time]).to eq(1800.0)
+ end
+ end
+ end
+end
From f812ca3b99ed0c72f185eda1890beb0433f44de6 Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 14:40:16 +0530
Subject: [PATCH 07/15] fix: code complexity issues
---
.../composables/useWhatsappEmbeddedSignup.js | 312 ++++--------------
.../whatsapp/useWhatsappFacebookSDK.js | 93 ++++++
.../whatsapp/useWhatsappSignupApi.js | 85 +++++
.../whatsapp/useWhatsappSignupHandlers.js | 58 ++++
.../whatsapp/useWhatsappSignupState.js | 34 ++
.../whatsapp/useWhatsappSignupValidation.js | 25 ++
6 files changed, 359 insertions(+), 248 deletions(-)
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js
diff --git a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
index 6adab5bf6..0cad25f5a 100644
--- a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
+++ b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
@@ -1,47 +1,74 @@
-import { ref, computed } from 'vue';
+import { computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
-import { useAlert } from 'dashboard/composables';
-import Auth from 'dashboard/api/auth';
+import { useWhatsappSignupValidation } from './whatsapp/useWhatsappSignupValidation';
+import { useWhatsappSignupState } from './whatsapp/useWhatsappSignupState';
+import { useWhatsappSignupHandlers } from './whatsapp/useWhatsappSignupHandlers';
+import { useWhatsappSignupApi } from './whatsapp/useWhatsappSignupApi';
+import { useWhatsappFacebookSDK } from './whatsapp/useWhatsappFacebookSDK';
export function useWhatsappEmbeddedSignup() {
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 currentStep = ref('initial');
- const authCode = ref(null);
- const businessData = ref(null);
- const isAuthenticating = ref(false);
- const hasSignupStarted = ref(false);
+ // Composables
+ const { isValidBusinessData, normalizeBusinessData } =
+ useWhatsappSignupValidation();
- // Computed
- const authHeaders = computed(() => {
- if (Auth.hasAuthCookie()) {
- const {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- } = Auth.getAuthData();
- return {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- };
- }
- return {};
+ const {
+ fbSdkLoaded,
+ isProcessing,
+ processingMessage,
+ authCodeReceived,
+ currentStep,
+ authCode,
+ businessData,
+ isAuthenticating,
+ hasSignupStarted,
+ resetState,
+ } = useWhatsappSignupState();
+
+ const { handleSignupError, handleSignupCancellation, handleSignupSuccess } =
+ useWhatsappSignupHandlers({
+ currentStep,
+ isProcessing,
+ isAuthenticating,
+ resetState,
+ store,
+ router,
+ t,
+ });
+
+ const { completeSignupFlow } = useWhatsappSignupApi({
+ authCodeReceived,
+ authCode,
+ currentStep,
+ isProcessing,
+ processingMessage,
+ store,
+ t,
+ handleSignupError,
+ handleSignupSuccess,
});
+ const { loadFacebookSdk, launchEmbeddedSignup } = useWhatsappFacebookSDK({
+ fbSdkLoaded,
+ hasSignupStarted,
+ processingMessage,
+ isProcessing,
+ isAuthenticating,
+ currentStep,
+ authCode,
+ authCodeReceived,
+ businessData,
+ completeSignupFlow,
+ handleSignupError,
+ t,
+ });
+
+ // Computed
const benefits = computed(() => [
{
key: 'EASY_SETUP',
@@ -61,148 +88,16 @@ export function useWhatsappEmbeddedSignup() {
() => hasSignupStarted.value || isProcessing.value
);
- // Error handling
- const handleSignupError = data => {
- currentStep.value = 'initial';
- isProcessing.value = false;
- authCodeReceived.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
-
- const errorMessage =
- data.error ||
- data.message ||
- t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
- useAlert(errorMessage);
- };
-
- const handleSignupCancellation = data => {
- currentStep.value = 'initial';
- isProcessing.value = false;
- authCodeReceived.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
-
- let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
- if (data.data?.current_step) {
- message += ` (Step: ${data.data.current_step})`;
- }
-
- useAlert(message);
- };
-
- const handleSignupSuccess = inboxData => {
- currentStep.value = 'completed';
- isProcessing.value = false;
- isAuthenticating.value = false;
-
- // Update the store with the new inbox data
- if (inboxData && inboxData.id) {
- // Add the new inbox to the store
- store.commit('inboxes/ADD_INBOXES', inboxData);
-
- useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
-
- router.replace({
- name: 'settings_inboxes_add_agents',
- params: {
- page: 'new',
- inbox_id: inboxData.id,
- },
- });
- } else {
- // Fallback if inbox data is not properly formatted
- 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;
- }
-
- currentStep.value = 'processing';
- isProcessing.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
- );
-
- try {
- // Send both auth code and business info together (synchronous flow)
- const accountId = store.getters.getCurrentAccountId;
- const response = await fetch('/whatsapp/embedded_signup', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRF-Token': document
- .querySelector('meta[name="csrf-token"]')
- ?.getAttribute('content'),
- ...authHeaders.value,
- },
- body: JSON.stringify({
- account_id: accountId,
- code: authCode.value,
- business_id: businessDataParam.business_id,
- waba_id: businessDataParam.waba_id,
- phone_number_id: businessDataParam.phone_number_id,
- }),
- });
-
- const responseData = await response.json();
-
- if (response.ok) {
- // Clear the stored auth code for security
- authCode.value = null;
-
- // Handle synchronous success response
- handleSignupSuccess(responseData);
- } else {
- throw new Error(responseData.message || responseData.error);
- }
- } catch (error) {
- handleSignupError({ error: error.message });
- }
- };
-
// Message handling
const handleEmbeddedSignupData = async data => {
- // Handle different embedded signup events per Facebook documentation
if (data.event === 'FINISH') {
- // Facebook might send business data in different structures
- let businessDataLocal = data.data;
+ const businessDataLocal =
+ data.data || data.business_data || data.details || data;
- // If data.data doesn't exist, try other possible structures
- if (!businessDataLocal) {
- businessDataLocal = data.business_data || data.details || data;
- }
-
- // Validate we have the required business information
- if (
- businessDataLocal &&
- (businessDataLocal.business_id || businessDataLocal.businessId) &&
- (businessDataLocal.waba_id || businessDataLocal.wabaId)
- ) {
- // Normalize the data structure to match our backend expectations
- const normalizedData = {
- business_id:
- businessDataLocal.business_id || businessDataLocal.businessId,
- waba_id: businessDataLocal.waba_id || businessDataLocal.wabaId,
- phone_number_id:
- businessDataLocal.phone_number_id ||
- businessDataLocal.phoneNumberId ||
- businessDataLocal.phone_id,
- };
-
- // Store business data
+ if (isValidBusinessData(businessDataLocal)) {
+ const normalizedData = normalizeBusinessData(businessDataLocal);
businessData.value = normalizedData;
- // Check if we already have auth code and process immediately
+
if (authCodeReceived.value && authCode.value) {
await completeSignupFlow(normalizedData);
} else {
@@ -231,45 +126,17 @@ export function useWhatsappEmbeddedSignup() {
}
};
- const fbLoginCallback = response => {
- if (response.authResponse && response.authResponse.code) {
- // Authorization code received from Facebook
- authCode.value = response.authResponse.code;
- authCodeReceived.value = true;
- currentStep.value = 'auth_received';
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
- );
-
- // Check if we already have business data and process immediately
- if (businessData.value) {
- completeSignupFlow(businessData.value);
- }
- } else if (response.error) {
- handleSignupError({ error: response.error });
- } else {
- currentStep.value = 'initial';
- isProcessing.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
- }
- };
-
const handleSignupMessage = event => {
- // Handle Facebook embedded signup message events
try {
const originUrl = new URL(event.origin);
const allowedHosts = ['facebook.com', 'www.facebook.com'];
if (!allowedHosts.includes(originUrl.hostname)) return;
} catch (error) {
- // Invalid origin URL, reject the event
return;
}
try {
const data = JSON.parse(event.data);
-
if (data.type === 'WA_EMBEDDED_SIGNUP') {
handleEmbeddedSignupData(data);
}
@@ -278,56 +145,6 @@ export function useWhatsappEmbeddedSignup() {
}
};
- // Facebook SDK
- const loadFacebookSdk = () => {
- if (window.FB) {
- fbSdkLoaded.value = true;
- return;
- }
- const script = document.createElement('script');
- script.src = 'https://connect.facebook.net/en_US/sdk.js';
- script.async = true;
- script.defer = true;
- script.onload = () => {
- window.FB.init({
- appId: window.chatwootConfig?.whatsappAppId,
- status: true,
- xfbml: true,
- version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
- });
- fbSdkLoaded.value = true;
- };
- document.body.appendChild(script);
- };
-
- const launchEmbeddedSignup = () => {
- hasSignupStarted.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
- );
-
- if (!window.FB) {
- loadFacebookSdk();
- setTimeout(() => launchEmbeddedSignup(), 1000);
- return;
- }
-
- isAuthenticating.value = true;
- currentStep.value = 'auth_processing';
-
- // Following Facebook's embedded signup documentation
- window.FB.login(fbLoginCallback, {
- config_id: window.chatwootConfig?.whatsappConfigurationId,
- response_type: 'code',
- override_default_response_type: true,
- extras: {
- setup: {},
- featureType: '', // Leave empty for default flow
- sessionInfoVersion: '3',
- },
- });
- };
-
// Lifecycle
const setupMessageListener = () => {
window.addEventListener('message', handleSignupMessage);
@@ -337,7 +154,6 @@ export function useWhatsappEmbeddedSignup() {
window.removeEventListener('message', handleSignupMessage);
};
- // Initialize
const initialize = () => {
loadFacebookSdk();
setupMessageListener();
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
new file mode 100644
index 000000000..dda13149f
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
@@ -0,0 +1,93 @@
+import { useAlert } from 'dashboard/composables';
+
+export function useWhatsappFacebookSDK({
+ fbSdkLoaded,
+ hasSignupStarted,
+ processingMessage,
+ isProcessing,
+ isAuthenticating,
+ currentStep,
+ authCode,
+ authCodeReceived,
+ businessData,
+ completeSignupFlow,
+ handleSignupError,
+ t,
+}) {
+ const loadFacebookSdk = () => {
+ if (window.FB) {
+ fbSdkLoaded.value = true;
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = 'https://connect.facebook.net/en_US/sdk.js';
+ script.async = true;
+ script.defer = true;
+ script.onload = () => {
+ window.FB.init({
+ appId: window.chatwootConfig?.whatsappAppId,
+ status: true,
+ xfbml: true,
+ version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
+ });
+ fbSdkLoaded.value = true;
+ };
+ document.body.appendChild(script);
+ };
+
+ const fbLoginCallback = response => {
+ if (response.authResponse && response.authResponse.code) {
+ authCode.value = response.authResponse.code;
+ authCodeReceived.value = true;
+ currentStep.value = 'auth_received';
+ 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 {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
+ }
+ };
+
+ const launchEmbeddedSignup = () => {
+ hasSignupStarted.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
+ );
+
+ if (!window.FB) {
+ loadFacebookSdk();
+ setTimeout(() => launchEmbeddedSignup(), 1000);
+ return;
+ }
+
+ isAuthenticating.value = true;
+ currentStep.value = 'auth_processing';
+
+ window.FB.login(fbLoginCallback, {
+ config_id: window.chatwootConfig?.whatsappConfigurationId,
+ response_type: 'code',
+ override_default_response_type: true,
+ extras: {
+ setup: {},
+ featureType: '',
+ sessionInfoVersion: '3',
+ },
+ });
+ };
+
+ return {
+ loadFacebookSdk,
+ launchEmbeddedSignup,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
new file mode 100644
index 000000000..9a762d1ec
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
@@ -0,0 +1,85 @@
+import { computed } from 'vue';
+import Auth from 'dashboard/api/auth';
+
+export function useWhatsappSignupApi({
+ authCodeReceived,
+ authCode,
+ currentStep,
+ isProcessing,
+ processingMessage,
+ store,
+ t,
+ handleSignupError,
+ handleSignupSuccess,
+}) {
+ const authHeaders = computed(() => {
+ if (Auth.hasAuthCookie()) {
+ const {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ } = Auth.getAuthData();
+ return {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ };
+ }
+ return {};
+ });
+
+ const completeSignupFlow = async businessDataParam => {
+ if (!authCodeReceived.value || !authCode.value) {
+ handleSignupError({
+ error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
+ });
+ return;
+ }
+
+ currentStep.value = 'processing';
+ isProcessing.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
+ );
+
+ try {
+ const accountId = store.getters.getCurrentAccountId;
+ const response = await fetch('/whatsapp/embedded_signup', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document
+ .querySelector('meta[name="csrf-token"]')
+ ?.getAttribute('content'),
+ ...authHeaders.value,
+ },
+ body: JSON.stringify({
+ account_id: accountId,
+ code: authCode.value,
+ business_id: businessDataParam.business_id,
+ waba_id: businessDataParam.waba_id,
+ phone_number_id: businessDataParam.phone_number_id,
+ }),
+ });
+
+ const responseData = await response.json();
+
+ if (response.ok) {
+ authCode.value = null;
+ handleSignupSuccess(responseData);
+ } else {
+ throw new Error(responseData.message || responseData.error);
+ }
+ } catch (error) {
+ handleSignupError({ error: error.message });
+ }
+ };
+
+ return {
+ completeSignupFlow,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
new file mode 100644
index 000000000..3a9b7d202
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
@@ -0,0 +1,58 @@
+import { useAlert } from 'dashboard/composables';
+
+export function useWhatsappSignupHandlers({
+ currentStep,
+ isProcessing,
+ isAuthenticating,
+ resetState,
+ store,
+ router,
+ t,
+}) {
+ const handleSignupError = data => {
+ resetState();
+ const errorMessage =
+ data.error ||
+ data.message ||
+ t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
+ useAlert(errorMessage);
+ };
+
+ const handleSignupCancellation = data => {
+ resetState();
+ let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
+ if (data.data?.current_step) {
+ message += ` (Step: ${data.data.current_step})`;
+ }
+ useAlert(message);
+ };
+
+ const handleSignupSuccess = inboxData => {
+ currentStep.value = 'completed';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+
+ if (inboxData && inboxData.id) {
+ store.commit('inboxes/ADD_INBOXES', inboxData);
+ 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',
+ });
+ }
+ };
+
+ return {
+ handleSignupError,
+ handleSignupCancellation,
+ handleSignupSuccess,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js
new file mode 100644
index 000000000..32ad3b7b7
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js
@@ -0,0 +1,34 @@
+import { ref } from 'vue';
+
+export function useWhatsappSignupState() {
+ const fbSdkLoaded = ref(false);
+ const isProcessing = ref(false);
+ const processingMessage = ref('');
+ const authCodeReceived = ref(false);
+ const currentStep = ref('initial');
+ const authCode = ref(null);
+ const businessData = ref(null);
+ const isAuthenticating = ref(false);
+ const hasSignupStarted = ref(false);
+
+ const resetState = () => {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ authCodeReceived.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+ };
+
+ return {
+ fbSdkLoaded,
+ isProcessing,
+ processingMessage,
+ authCodeReceived,
+ currentStep,
+ authCode,
+ businessData,
+ isAuthenticating,
+ hasSignupStarted,
+ resetState,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js
new file mode 100644
index 000000000..ea9babaa4
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js
@@ -0,0 +1,25 @@
+export function useWhatsappSignupValidation() {
+ const isValidBusinessData = businessData => {
+ return (
+ businessData &&
+ (businessData.business_id || businessData.businessId) &&
+ (businessData.waba_id || businessData.wabaId)
+ );
+ };
+
+ const normalizeBusinessData = businessData => {
+ return {
+ business_id: businessData.business_id || businessData.businessId,
+ waba_id: businessData.waba_id || businessData.wabaId,
+ phone_number_id:
+ businessData.phone_number_id ||
+ businessData.phoneNumberId ||
+ businessData.phone_id,
+ };
+ };
+
+ return {
+ isValidBusinessData,
+ normalizeBusinessData,
+ };
+}
From 65bb584f489ac8feed7d6812d4f85d46421392eb Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 14:48:15 +0530
Subject: [PATCH 08/15] chore: more code cleanups
---
.../composables/useWhatsappEmbeddedSignup.js | 72 +++-------
.../whatsapp/useWhatsappFacebookSDK.js | 127 +++++++++++-------
.../whatsapp/useWhatsappMessageHandler.js | 96 +++++++++++++
.../whatsapp/useWhatsappSignupApi.js | 87 +++++++-----
.../whatsapp/useWhatsappSignupHandlers.js | 74 +++++++---
5 files changed, 296 insertions(+), 160 deletions(-)
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
diff --git a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
index 0cad25f5a..a87c70dfc 100644
--- a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
+++ b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
@@ -7,6 +7,7 @@ import { useWhatsappSignupState } from './whatsapp/useWhatsappSignupState';
import { useWhatsappSignupHandlers } from './whatsapp/useWhatsappSignupHandlers';
import { useWhatsappSignupApi } from './whatsapp/useWhatsappSignupApi';
import { useWhatsappFacebookSDK } from './whatsapp/useWhatsappFacebookSDK';
+import { useWhatsappMessageHandler } from './whatsapp/useWhatsappMessageHandler';
export function useWhatsappEmbeddedSignup() {
const store = useStore();
@@ -68,6 +69,20 @@ export function useWhatsappEmbeddedSignup() {
t,
});
+ const { handleSignupMessage } = useWhatsappMessageHandler({
+ isValidBusinessData,
+ normalizeBusinessData,
+ businessData,
+ authCodeReceived,
+ authCode,
+ completeSignupFlow,
+ currentStep,
+ processingMessage,
+ handleSignupError,
+ handleSignupCancellation,
+ t,
+ });
+
// Computed
const benefits = computed(() => [
{
@@ -88,63 +103,6 @@ export function useWhatsappEmbeddedSignup() {
() => hasSignupStarted.value || isProcessing.value
);
- // Message handling
- const handleEmbeddedSignupData = async data => {
- if (data.event === 'FINISH') {
- const businessDataLocal =
- data.data || data.business_data || data.details || data;
-
- if (isValidBusinessData(businessDataLocal)) {
- const normalizedData = normalizeBusinessData(businessDataLocal);
- businessData.value = normalizedData;
-
- if (authCodeReceived.value && authCode.value) {
- await completeSignupFlow(normalizedData);
- } else {
- currentStep.value = 'waiting_for_auth';
- 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(data);
- } 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 handleSignupMessage = event => {
- try {
- const originUrl = new URL(event.origin);
- const allowedHosts = ['facebook.com', 'www.facebook.com'];
- if (!allowedHosts.includes(originUrl.hostname)) return;
- } catch (error) {
- return;
- }
-
- try {
- const data = JSON.parse(event.data);
- if (data.type === 'WA_EMBEDDED_SIGNUP') {
- handleEmbeddedSignupData(data);
- }
- } catch (error) {
- // Handle non-JSON messages silently
- }
- };
-
// Lifecycle
const setupMessageListener = () => {
window.addEventListener('message', handleSignupMessage);
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
index dda13149f..4f55452e2 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
@@ -14,56 +14,99 @@ export function useWhatsappFacebookSDK({
handleSignupError,
t,
}) {
+ const initializeFacebookSDK = () => {
+ window.FB.init({
+ appId: window.chatwootConfig?.whatsappAppId,
+ status: true,
+ xfbml: true,
+ version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
+ });
+ fbSdkLoaded.value = true;
+ };
+
+ const createFacebookScript = () => {
+ const script = document.createElement('script');
+ script.src = 'https://connect.facebook.net/en_US/sdk.js';
+ script.async = true;
+ script.defer = true;
+ script.onload = initializeFacebookSDK;
+ document.body.appendChild(script);
+ };
+
const loadFacebookSdk = () => {
if (window.FB) {
fbSdkLoaded.value = true;
return;
}
-
- const script = document.createElement('script');
- script.src = 'https://connect.facebook.net/en_US/sdk.js';
- script.async = true;
- script.defer = true;
- script.onload = () => {
- window.FB.init({
- appId: window.chatwootConfig?.whatsappAppId,
- status: true,
- xfbml: true,
- version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
- });
- fbSdkLoaded.value = true;
- };
- document.body.appendChild(script);
+ createFacebookScript();
};
- const fbLoginCallback = response => {
- if (response.authResponse && response.authResponse.code) {
- authCode.value = response.authResponse.code;
- authCodeReceived.value = true;
- currentStep.value = 'auth_received';
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
- );
+ const handleSuccessfulAuth = authResponse => {
+ authCode.value = authResponse.code;
+ authCodeReceived.value = true;
+ currentStep.value = 'auth_received';
+ 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 {
- currentStep.value = 'initial';
- isProcessing.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
+ if (businessData.value) {
+ completeSignupFlow(businessData.value);
}
};
- const launchEmbeddedSignup = () => {
+ const handleAuthError = error => {
+ handleSignupError({ error });
+ };
+
+ const handleAuthCancellation = () => {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
+ };
+
+ const fbLoginCallback = response => {
+ if (response.authResponse?.code) {
+ handleSuccessfulAuth(response.authResponse);
+ } else if (response.error) {
+ handleAuthError(response.error);
+ } else {
+ handleAuthCancellation();
+ }
+ };
+
+ const setSignupProcessingState = () => {
hasSignupStarted.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
);
+ };
+
+ const setAuthenticatingState = () => {
+ isAuthenticating.value = true;
+ currentStep.value = 'auth_processing';
+ };
+
+ const createLoginOptions = () => ({
+ config_id: window.chatwootConfig?.whatsappConfigurationId,
+ response_type: 'code',
+ override_default_response_type: true,
+ extras: {
+ setup: {},
+ featureType: '',
+ sessionInfoVersion: '3',
+ },
+ });
+
+ const executeSignup = () => {
+ setAuthenticatingState();
+ const options = createLoginOptions();
+ window.FB.login(fbLoginCallback, options);
+ };
+
+ const launchEmbeddedSignup = () => {
+ setSignupProcessingState();
if (!window.FB) {
loadFacebookSdk();
@@ -71,19 +114,7 @@ export function useWhatsappFacebookSDK({
return;
}
- isAuthenticating.value = true;
- currentStep.value = 'auth_processing';
-
- window.FB.login(fbLoginCallback, {
- config_id: window.chatwootConfig?.whatsappConfigurationId,
- response_type: 'code',
- override_default_response_type: true,
- extras: {
- setup: {},
- featureType: '',
- sessionInfoVersion: '3',
- },
- });
+ executeSignup();
};
return {
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
new file mode 100644
index 000000000..000c0c60e
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
@@ -0,0 +1,96 @@
+export function useWhatsappMessageHandler({
+ isValidBusinessData,
+ normalizeBusinessData,
+ businessData,
+ authCodeReceived,
+ authCode,
+ completeSignupFlow,
+ currentStep,
+ processingMessage,
+ handleSignupError,
+ handleSignupCancellation,
+ t,
+}) {
+ const processFinishEvent = async data => {
+ const businessDataLocal =
+ data.data || data.business_data || data.details || data;
+
+ if (isValidBusinessData(businessDataLocal)) {
+ const normalizedData = normalizeBusinessData(businessDataLocal);
+ businessData.value = normalizedData;
+
+ if (authCodeReceived.value && authCode.value) {
+ await completeSignupFlow(normalizedData);
+ } else {
+ currentStep.value = 'waiting_for_auth';
+ 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'
+ ),
+ });
+ }
+ };
+
+ const processErrorEvent = data => {
+ 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 handleEmbeddedSignupData = async data => {
+ switch (data.event) {
+ case 'FINISH':
+ await processFinishEvent(data);
+ break;
+ case 'CANCEL':
+ handleSignupCancellation(data);
+ break;
+ case 'error':
+ processErrorEvent(data);
+ break;
+ default:
+ // Handle unknown events silently
+ break;
+ }
+ };
+
+ const validateMessageOrigin = origin => {
+ try {
+ const originUrl = new URL(origin);
+ const allowedHosts = ['facebook.com', 'www.facebook.com'];
+ return allowedHosts.includes(originUrl.hostname);
+ } catch (error) {
+ return false;
+ }
+ };
+
+ const parseMessageData = data => {
+ try {
+ return JSON.parse(data);
+ } catch (error) {
+ return null;
+ }
+ };
+
+ const handleSignupMessage = event => {
+ if (!validateMessageOrigin(event.origin)) return;
+
+ const data = parseMessageData(event.data);
+ if (data?.type === 'WA_EMBEDDED_SIGNUP') {
+ handleEmbeddedSignupData(data);
+ }
+ };
+
+ return {
+ handleSignupMessage,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
index 9a762d1ec..a93405f27 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
@@ -32,48 +32,69 @@ export function useWhatsappSignupApi({
return {};
});
+ const validateAuthRequirements = () => {
+ return authCodeReceived.value && authCode.value;
+ };
+
+ const createSignupPayload = businessDataParam => {
+ const accountId = store.getters.getCurrentAccountId;
+ return {
+ account_id: accountId,
+ code: authCode.value,
+ business_id: businessDataParam.business_id,
+ waba_id: businessDataParam.waba_id,
+ phone_number_id: businessDataParam.phone_number_id,
+ };
+ };
+
+ const createFetchOptions = payload => {
+ return {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document
+ .querySelector('meta[name="csrf-token"]')
+ ?.getAttribute('content'),
+ ...authHeaders.value,
+ },
+ body: JSON.stringify(payload),
+ };
+ };
+
+ const setProcessingState = () => {
+ currentStep.value = 'processing';
+ isProcessing.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
+ );
+ };
+
+ const handleApiResponse = async response => {
+ const responseData = await response.json();
+
+ if (response.ok) {
+ authCode.value = null;
+ handleSignupSuccess(responseData);
+ } else {
+ throw new Error(responseData.message || responseData.error);
+ }
+ };
+
const completeSignupFlow = async businessDataParam => {
- if (!authCodeReceived.value || !authCode.value) {
+ if (!validateAuthRequirements()) {
handleSignupError({
error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
});
return;
}
- currentStep.value = 'processing';
- isProcessing.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
- );
+ setProcessingState();
try {
- const accountId = store.getters.getCurrentAccountId;
- const response = await fetch('/whatsapp/embedded_signup', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRF-Token': document
- .querySelector('meta[name="csrf-token"]')
- ?.getAttribute('content'),
- ...authHeaders.value,
- },
- body: JSON.stringify({
- account_id: accountId,
- code: authCode.value,
- business_id: businessDataParam.business_id,
- waba_id: businessDataParam.waba_id,
- phone_number_id: businessDataParam.phone_number_id,
- }),
- });
-
- const responseData = await response.json();
-
- if (response.ok) {
- authCode.value = null;
- handleSignupSuccess(responseData);
- } else {
- throw new Error(responseData.message || responseData.error);
- }
+ const payload = createSignupPayload(businessDataParam);
+ const options = createFetchOptions(payload);
+ const response = await fetch('/whatsapp/embedded_signup', options);
+ await handleApiResponse(response);
} catch (error) {
handleSignupError({ error: error.message });
}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
index 3a9b7d202..9cc4fae06 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
@@ -9,44 +9,74 @@ export function useWhatsappSignupHandlers({
router,
t,
}) {
- const handleSignupError = data => {
- resetState();
- const errorMessage =
+ const getErrorMessage = data => {
+ return (
data.error ||
data.message ||
- t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
+ t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE')
+ );
+ };
+
+ const getCancellationMessage = data => {
+ let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
+ if (data.data?.current_step) {
+ message += ` (Step: ${data.data.current_step})`;
+ }
+ return message;
+ };
+
+ const handleSignupError = data => {
+ resetState();
+ const errorMessage = getErrorMessage(data);
useAlert(errorMessage);
};
const handleSignupCancellation = data => {
resetState();
- let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
- if (data.data?.current_step) {
- message += ` (Step: ${data.data.current_step})`;
- }
+ const message = getCancellationMessage(data);
useAlert(message);
};
+ const navigateToAgentSelection = inboxId => {
+ router.replace({
+ name: 'settings_inboxes_add_agents',
+ params: {
+ page: 'new',
+ inbox_id: inboxId,
+ },
+ });
+ };
+
+ const navigateToInboxList = () => {
+ router.replace({
+ name: 'settings_inbox_list',
+ });
+ };
+
+ const updateStoreWithInbox = inboxData => {
+ store.commit('inboxes/ADD_INBOXES', inboxData);
+ useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
+ };
+
+ const handleValidInboxData = inboxData => {
+ updateStoreWithInbox(inboxData);
+ navigateToAgentSelection(inboxData.id);
+ };
+
+ const handleInvalidInboxData = () => {
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
+ navigateToInboxList();
+ };
+
const handleSignupSuccess = inboxData => {
currentStep.value = 'completed';
isProcessing.value = false;
isAuthenticating.value = false;
- if (inboxData && inboxData.id) {
- store.commit('inboxes/ADD_INBOXES', inboxData);
- useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
- router.replace({
- name: 'settings_inboxes_add_agents',
- params: {
- page: 'new',
- inbox_id: inboxData.id,
- },
- });
+ if (inboxData?.id) {
+ handleValidInboxData(inboxData);
} else {
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
- router.replace({
- name: 'settings_inbox_list',
- });
+ handleInvalidInboxData();
}
};
From 48293001ed9dc41ec0c4473d3e02899ae2112c31 Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 14:52:48 +0530
Subject: [PATCH 09/15] chore: fix issues
---
.../whatsapp/useWhatsappApiRequest.js | 52 ++++++++++
.../composables/whatsapp/useWhatsappAuth.js | 33 +++++++
.../whatsapp/useWhatsappSignupApi.js | 95 ++++---------------
.../whatsapp/useWhatsappSignupFlow.js | 32 +++++++
4 files changed, 136 insertions(+), 76 deletions(-)
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js
new file mode 100644
index 000000000..ed6c2e875
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js
@@ -0,0 +1,52 @@
+export function useWhatsappApiRequest() {
+ const createSignupPayload = (authCode, store, businessDataParam) => {
+ const accountId = store.getters.getCurrentAccountId;
+ return {
+ account_id: accountId,
+ code: authCode.value,
+ business_id: businessDataParam.business_id,
+ waba_id: businessDataParam.waba_id,
+ phone_number_id: businessDataParam.phone_number_id,
+ };
+ };
+
+ const createFetchOptions = (payload, authHeaders) => {
+ return {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document
+ .querySelector('meta[name="csrf-token"]')
+ ?.getAttribute('content'),
+ ...authHeaders.value,
+ },
+ body: JSON.stringify(payload),
+ };
+ };
+
+ const makeSignupRequest = async (payload, authHeaders) => {
+ const options = createFetchOptions(payload, authHeaders);
+ return fetch('/whatsapp/embedded_signup', options);
+ };
+
+ const processApiResponse = async (
+ response,
+ authCode,
+ handleSignupSuccess
+ ) => {
+ const responseData = await response.json();
+
+ if (response.ok) {
+ authCode.value = null;
+ handleSignupSuccess(responseData);
+ } else {
+ throw new Error(responseData.message || responseData.error);
+ }
+ };
+
+ return {
+ createSignupPayload,
+ makeSignupRequest,
+ processApiResponse,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js
new file mode 100644
index 000000000..888d1a03b
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js
@@ -0,0 +1,33 @@
+import { computed } from 'vue';
+import Auth from 'dashboard/api/auth';
+
+export function useWhatsappAuth() {
+ const authHeaders = computed(() => {
+ if (!Auth.hasAuthCookie()) return {};
+
+ const {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ } = Auth.getAuthData();
+
+ return {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ };
+ });
+
+ const validateAuthRequirements = (authCodeReceived, authCode) => {
+ return authCodeReceived.value && authCode.value;
+ };
+
+ return {
+ authHeaders,
+ validateAuthRequirements,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
index a93405f27..ba51e9f37 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
@@ -1,5 +1,6 @@
-import { computed } from 'vue';
-import Auth from 'dashboard/api/auth';
+import { useWhatsappAuth } from './useWhatsappAuth';
+import { useWhatsappApiRequest } from './useWhatsappApiRequest';
+import { useWhatsappSignupFlow } from './useWhatsappSignupFlow';
export function useWhatsappSignupApi({
authCodeReceived,
@@ -12,91 +13,33 @@ export function useWhatsappSignupApi({
handleSignupError,
handleSignupSuccess,
}) {
- const authHeaders = computed(() => {
- if (Auth.hasAuthCookie()) {
- const {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- } = Auth.getAuthData();
- return {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- };
- }
- return {};
- });
+ const { authHeaders, validateAuthRequirements } = useWhatsappAuth();
- const validateAuthRequirements = () => {
- return authCodeReceived.value && authCode.value;
- };
+ const { createSignupPayload, makeSignupRequest, processApiResponse } =
+ useWhatsappApiRequest();
- const createSignupPayload = businessDataParam => {
- const accountId = store.getters.getCurrentAccountId;
- return {
- account_id: accountId,
- code: authCode.value,
- business_id: businessDataParam.business_id,
- waba_id: businessDataParam.waba_id,
- phone_number_id: businessDataParam.phone_number_id,
- };
- };
-
- const createFetchOptions = payload => {
- return {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRF-Token': document
- .querySelector('meta[name="csrf-token"]')
- ?.getAttribute('content'),
- ...authHeaders.value,
- },
- body: JSON.stringify(payload),
- };
- };
-
- const setProcessingState = () => {
- currentStep.value = 'processing';
- isProcessing.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
- );
- };
-
- const handleApiResponse = async response => {
- const responseData = await response.json();
-
- if (response.ok) {
- authCode.value = null;
- handleSignupSuccess(responseData);
- } else {
- throw new Error(responseData.message || responseData.error);
- }
- };
+ const { setProcessingState, handleAuthError, handleRequestError } =
+ useWhatsappSignupFlow({
+ currentStep,
+ isProcessing,
+ processingMessage,
+ t,
+ });
const completeSignupFlow = async businessDataParam => {
- if (!validateAuthRequirements()) {
- handleSignupError({
- error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
- });
+ if (!validateAuthRequirements(authCodeReceived, authCode)) {
+ handleAuthError(handleSignupError, t);
return;
}
setProcessingState();
try {
- const payload = createSignupPayload(businessDataParam);
- const options = createFetchOptions(payload);
- const response = await fetch('/whatsapp/embedded_signup', options);
- await handleApiResponse(response);
+ const payload = createSignupPayload(authCode, store, businessDataParam);
+ const response = await makeSignupRequest(payload, authHeaders);
+ await processApiResponse(response, authCode, handleSignupSuccess);
} catch (error) {
- handleSignupError({ error: error.message });
+ handleRequestError(error, handleSignupError);
}
};
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js
new file mode 100644
index 000000000..237e1ea14
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js
@@ -0,0 +1,32 @@
+export function useWhatsappSignupFlow({
+ currentStep,
+ isProcessing,
+ processingMessage,
+ t,
+}) {
+ const setProcessingState = () => {
+ currentStep.value = 'processing';
+ isProcessing.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
+ );
+ };
+
+ const handleAuthError = (handleSignupError, translator) => {
+ handleSignupError({
+ error: translator(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'
+ ),
+ });
+ };
+
+ const handleRequestError = (error, handleSignupError) => {
+ handleSignupError({ error: error.message });
+ };
+
+ return {
+ setProcessingState,
+ handleAuthError,
+ handleRequestError,
+ };
+}
From 2f8214eb870a769bebb8c5aa1fc3efd4ce1da61c Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 15:01:53 +0530
Subject: [PATCH 10/15] chore: more fixes
---
.../composables/useWhatsappEmbeddedSignup.js | 138 ++----------------
.../useWhatsappComposableOrchestrator.js | 100 +++++++++++++
.../whatsapp/useWhatsappComputed.js | 27 ++++
.../whatsapp/useWhatsappLifecycle.js | 19 +++
4 files changed, 161 insertions(+), 123 deletions(-)
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js
diff --git a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
index a87c70dfc..0bf6d134e 100644
--- a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
+++ b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
@@ -1,141 +1,33 @@
-import { computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
-import { useWhatsappSignupValidation } from './whatsapp/useWhatsappSignupValidation';
-import { useWhatsappSignupState } from './whatsapp/useWhatsappSignupState';
-import { useWhatsappSignupHandlers } from './whatsapp/useWhatsappSignupHandlers';
-import { useWhatsappSignupApi } from './whatsapp/useWhatsappSignupApi';
-import { useWhatsappFacebookSDK } from './whatsapp/useWhatsappFacebookSDK';
-import { useWhatsappMessageHandler } from './whatsapp/useWhatsappMessageHandler';
+import { useWhatsappComposableOrchestrator } from './whatsapp/useWhatsappComposableOrchestrator';
+import { useWhatsappLifecycle } from './whatsapp/useWhatsappLifecycle';
+import { useWhatsappComputed } from './whatsapp/useWhatsappComputed';
export function useWhatsappEmbeddedSignup() {
const store = useStore();
const router = useRouter();
const { t } = useI18n();
- // Composables
- const { isValidBusinessData, normalizeBusinessData } =
- useWhatsappSignupValidation();
-
- const {
- fbSdkLoaded,
- isProcessing,
- processingMessage,
- authCodeReceived,
- currentStep,
- authCode,
- businessData,
- isAuthenticating,
- hasSignupStarted,
- resetState,
- } = useWhatsappSignupState();
-
- const { handleSignupError, handleSignupCancellation, handleSignupSuccess } =
- useWhatsappSignupHandlers({
- currentStep,
- isProcessing,
- isAuthenticating,
- resetState,
- store,
- router,
- t,
- });
-
- const { completeSignupFlow } = useWhatsappSignupApi({
- authCodeReceived,
- authCode,
- currentStep,
- isProcessing,
- processingMessage,
+ const orchestratorResult = useWhatsappComposableOrchestrator({
store,
- t,
- handleSignupError,
- handleSignupSuccess,
- });
-
- const { loadFacebookSdk, launchEmbeddedSignup } = useWhatsappFacebookSDK({
- fbSdkLoaded,
- hasSignupStarted,
- processingMessage,
- isProcessing,
- isAuthenticating,
- currentStep,
- authCode,
- authCodeReceived,
- businessData,
- completeSignupFlow,
- handleSignupError,
+ router,
t,
});
-
- const { handleSignupMessage } = useWhatsappMessageHandler({
- isValidBusinessData,
- normalizeBusinessData,
- businessData,
- authCodeReceived,
- authCode,
- completeSignupFlow,
- currentStep,
- processingMessage,
- handleSignupError,
- handleSignupCancellation,
+ const computedResult = useWhatsappComputed({
t,
+ hasSignupStarted: orchestratorResult.hasSignupStarted,
+ isProcessing: orchestratorResult.isProcessing,
+ });
+ const lifecycleResult = useWhatsappLifecycle({
+ loadFacebookSdk: orchestratorResult.loadFacebookSdk,
+ handleSignupMessage: orchestratorResult.handleSignupMessage,
});
-
- // Computed
- 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(
- () => hasSignupStarted.value || isProcessing.value
- );
-
- // Lifecycle
- const setupMessageListener = () => {
- window.addEventListener('message', handleSignupMessage);
- };
-
- const cleanupMessageListener = () => {
- window.removeEventListener('message', handleSignupMessage);
- };
-
- const initialize = () => {
- loadFacebookSdk();
- setupMessageListener();
- };
return {
- // State
- fbSdkLoaded,
- isProcessing,
- processingMessage,
- authCodeReceived,
- currentStep,
- authCode,
- businessData,
- isAuthenticating,
- hasSignupStarted,
-
- // Computed
- benefits,
- showLoader,
-
- // Methods
- launchEmbeddedSignup,
- initialize,
- cleanupMessageListener,
+ ...orchestratorResult,
+ ...computedResult,
+ ...lifecycleResult,
};
}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js
new file mode 100644
index 000000000..9e9182476
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js
@@ -0,0 +1,100 @@
+import { useWhatsappSignupValidation } from './useWhatsappSignupValidation';
+import { useWhatsappSignupState } from './useWhatsappSignupState';
+import { useWhatsappSignupHandlers } from './useWhatsappSignupHandlers';
+import { useWhatsappSignupApi } from './useWhatsappSignupApi';
+import { useWhatsappFacebookSDK } from './useWhatsappFacebookSDK';
+import { useWhatsappMessageHandler } from './useWhatsappMessageHandler';
+
+export function useWhatsappComposableOrchestrator({ store, router, t }) {
+ // Validation composable
+ const { isValidBusinessData, normalizeBusinessData } =
+ useWhatsappSignupValidation();
+
+ // State composable
+ const {
+ fbSdkLoaded,
+ isProcessing,
+ processingMessage,
+ authCodeReceived,
+ currentStep,
+ authCode,
+ businessData,
+ isAuthenticating,
+ hasSignupStarted,
+ resetState,
+ } = useWhatsappSignupState();
+
+ // Handlers composable
+ const { handleSignupError, handleSignupCancellation, handleSignupSuccess } =
+ useWhatsappSignupHandlers({
+ currentStep,
+ isProcessing,
+ isAuthenticating,
+ resetState,
+ store,
+ router,
+ t,
+ });
+
+ // API composable
+ const { completeSignupFlow } = useWhatsappSignupApi({
+ authCodeReceived,
+ authCode,
+ currentStep,
+ isProcessing,
+ processingMessage,
+ store,
+ t,
+ handleSignupError,
+ handleSignupSuccess,
+ });
+
+ // Facebook SDK composable
+ const { loadFacebookSdk, launchEmbeddedSignup } = useWhatsappFacebookSDK({
+ fbSdkLoaded,
+ hasSignupStarted,
+ processingMessage,
+ isProcessing,
+ isAuthenticating,
+ currentStep,
+ authCode,
+ authCodeReceived,
+ businessData,
+ completeSignupFlow,
+ handleSignupError,
+ t,
+ });
+
+ // Message handler composable
+ const { handleSignupMessage } = useWhatsappMessageHandler({
+ isValidBusinessData,
+ normalizeBusinessData,
+ businessData,
+ authCodeReceived,
+ authCode,
+ completeSignupFlow,
+ currentStep,
+ processingMessage,
+ handleSignupError,
+ handleSignupCancellation,
+ t,
+ });
+
+ return {
+ // State
+ fbSdkLoaded,
+ isProcessing,
+ processingMessage,
+ authCodeReceived,
+ currentStep,
+ authCode,
+ businessData,
+ isAuthenticating,
+ hasSignupStarted,
+
+ // Methods
+ loadFacebookSdk,
+ launchEmbeddedSignup,
+ handleSignupMessage,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js
new file mode 100644
index 000000000..dd1f58a86
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js
@@ -0,0 +1,27 @@
+import { computed } from 'vue';
+
+export function useWhatsappComputed({ t, hasSignupStarted, isProcessing }) {
+ 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(
+ () => hasSignupStarted.value || isProcessing.value
+ );
+
+ return {
+ benefits,
+ showLoader,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js
new file mode 100644
index 000000000..20985caed
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js
@@ -0,0 +1,19 @@
+export function useWhatsappLifecycle({ loadFacebookSdk, handleSignupMessage }) {
+ const setupMessageListener = () => {
+ window.addEventListener('message', handleSignupMessage);
+ };
+
+ const cleanupMessageListener = () => {
+ window.removeEventListener('message', handleSignupMessage);
+ };
+
+ const initialize = () => {
+ loadFacebookSdk();
+ setupMessageListener();
+ };
+
+ return {
+ initialize,
+ cleanupMessageListener,
+ };
+}
From 6e1c3ee1760e791887149349a9acc547ff9ec43b Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 15:07:43 +0530
Subject: [PATCH 11/15] chore: add more helpers
---
.../whatsapp/useWhatsappAuthCallbacks.js | 54 +++++++++++
.../whatsapp/useWhatsappEventProcessors.js | 58 ++++++++++++
.../whatsapp/useWhatsappFacebookSDK.js | 88 ++++--------------
.../whatsapp/useWhatsappMessageHandler.js | 90 ++++++-------------
.../whatsapp/useWhatsappMessageValidation.js | 29 ++++++
.../whatsapp/useWhatsappSDKHelpers.js | 37 ++++++++
.../whatsapp/useWhatsappSignupHandlers.js | 35 +-------
.../whatsapp/useWhatsappSuccessHandler.js | 39 ++++++++
8 files changed, 266 insertions(+), 164 deletions(-)
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js
create mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js
new file mode 100644
index 000000000..101069b47
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js
@@ -0,0 +1,54 @@
+import { useAlert } from 'dashboard/composables';
+
+export function useWhatsappAuthCallbacks({
+ authCode,
+ authCodeReceived,
+ currentStep,
+ processingMessage,
+ isProcessing,
+ isAuthenticating,
+ hasSignupStarted,
+ businessData,
+ completeSignupFlow,
+ handleSignupError,
+ t,
+}) {
+ const handleSuccessfulAuth = authResponse => {
+ authCode.value = authResponse.code;
+ authCodeReceived.value = true;
+ currentStep.value = 'auth_received';
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
+ );
+
+ if (businessData.value) {
+ completeSignupFlow(businessData.value);
+ }
+ };
+
+ const handleAuthError = error => {
+ handleSignupError({ error });
+ };
+
+ const handleAuthCancellation = () => {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
+ };
+
+ const fbLoginCallback = response => {
+ if (response.authResponse?.code) {
+ handleSuccessfulAuth(response.authResponse);
+ } else if (response.error) {
+ handleAuthError(response.error);
+ } else {
+ handleAuthCancellation();
+ }
+ };
+
+ return {
+ fbLoginCallback,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js
new file mode 100644
index 000000000..550a23807
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js
@@ -0,0 +1,58 @@
+export function useWhatsappEventProcessors({
+ isValidBusinessData,
+ normalizeBusinessData,
+ businessData,
+ authCodeReceived,
+ authCode,
+ completeSignupFlow,
+ currentStep,
+ processingMessage,
+ handleSignupError,
+ handleSignupCancellation,
+ t,
+}) {
+ const processFinishEvent = async data => {
+ const businessDataLocal =
+ data.data || data.business_data || data.details || data;
+
+ if (isValidBusinessData(businessDataLocal)) {
+ const normalizedData = normalizeBusinessData(businessDataLocal);
+ businessData.value = normalizedData;
+
+ if (authCodeReceived.value && authCode.value) {
+ await completeSignupFlow(normalizedData);
+ } else {
+ currentStep.value = 'waiting_for_auth';
+ 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'
+ ),
+ });
+ }
+ };
+
+ const processErrorEvent = data => {
+ 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 processCancelEvent = data => {
+ handleSignupCancellation(data);
+ };
+
+ return {
+ processFinishEvent,
+ processErrorEvent,
+ processCancelEvent,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
index 4f55452e2..046b3baba 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
@@ -1,4 +1,5 @@
-import { useAlert } from 'dashboard/composables';
+import { useWhatsappSDKHelpers } from './useWhatsappSDKHelpers';
+import { useWhatsappAuthCallbacks } from './useWhatsappAuthCallbacks';
export function useWhatsappFacebookSDK({
fbSdkLoaded,
@@ -14,66 +15,28 @@ export function useWhatsappFacebookSDK({
handleSignupError,
t,
}) {
- const initializeFacebookSDK = () => {
- window.FB.init({
- appId: window.chatwootConfig?.whatsappAppId,
- status: true,
- xfbml: true,
- version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
- });
- fbSdkLoaded.value = true;
- };
+ const { createFacebookScript, createLoginOptions } = useWhatsappSDKHelpers();
- const createFacebookScript = () => {
- const script = document.createElement('script');
- script.src = 'https://connect.facebook.net/en_US/sdk.js';
- script.async = true;
- script.defer = true;
- script.onload = initializeFacebookSDK;
- document.body.appendChild(script);
- };
+ const { fbLoginCallback } = useWhatsappAuthCallbacks({
+ authCode,
+ authCodeReceived,
+ currentStep,
+ processingMessage,
+ isProcessing,
+ isAuthenticating,
+ hasSignupStarted,
+ businessData,
+ completeSignupFlow,
+ handleSignupError,
+ t,
+ });
const loadFacebookSdk = () => {
if (window.FB) {
fbSdkLoaded.value = true;
return;
}
- createFacebookScript();
- };
-
- const handleSuccessfulAuth = authResponse => {
- authCode.value = authResponse.code;
- authCodeReceived.value = true;
- currentStep.value = 'auth_received';
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
- );
-
- if (businessData.value) {
- completeSignupFlow(businessData.value);
- }
- };
-
- const handleAuthError = error => {
- handleSignupError({ error });
- };
-
- const handleAuthCancellation = () => {
- currentStep.value = 'initial';
- isProcessing.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
- };
-
- const fbLoginCallback = response => {
- if (response.authResponse?.code) {
- handleSuccessfulAuth(response.authResponse);
- } else if (response.error) {
- handleAuthError(response.error);
- } else {
- handleAuthCancellation();
- }
+ createFacebookScript(fbSdkLoaded);
};
const setSignupProcessingState = () => {
@@ -83,24 +46,9 @@ export function useWhatsappFacebookSDK({
);
};
- const setAuthenticatingState = () => {
+ const executeSignup = () => {
isAuthenticating.value = true;
currentStep.value = 'auth_processing';
- };
-
- const createLoginOptions = () => ({
- config_id: window.chatwootConfig?.whatsappConfigurationId,
- response_type: 'code',
- override_default_response_type: true,
- extras: {
- setup: {},
- featureType: '',
- sessionInfoVersion: '3',
- },
- });
-
- const executeSignup = () => {
- setAuthenticatingState();
const options = createLoginOptions();
window.FB.login(fbLoginCallback, options);
};
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
index 000c0c60e..5131e557c 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
@@ -1,3 +1,6 @@
+import { useWhatsappMessageValidation } from './useWhatsappMessageValidation';
+import { useWhatsappEventProcessors } from './useWhatsappEventProcessors';
+
export function useWhatsappMessageHandler({
isValidBusinessData,
normalizeBusinessData,
@@ -11,73 +14,34 @@ export function useWhatsappMessageHandler({
handleSignupCancellation,
t,
}) {
- const processFinishEvent = async data => {
- const businessDataLocal =
- data.data || data.business_data || data.details || data;
+ const { validateMessageOrigin, parseMessageData, isWhatsappSignupMessage } =
+ useWhatsappMessageValidation();
- if (isValidBusinessData(businessDataLocal)) {
- const normalizedData = normalizeBusinessData(businessDataLocal);
- businessData.value = normalizedData;
-
- if (authCodeReceived.value && authCode.value) {
- await completeSignupFlow(normalizedData);
- } else {
- currentStep.value = 'waiting_for_auth';
- 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'
- ),
- });
- }
- };
-
- const processErrorEvent = data => {
- 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 { processFinishEvent, processErrorEvent, processCancelEvent } =
+ useWhatsappEventProcessors({
+ isValidBusinessData,
+ normalizeBusinessData,
+ businessData,
+ authCodeReceived,
+ authCode,
+ completeSignupFlow,
+ currentStep,
+ processingMessage,
+ handleSignupError,
+ handleSignupCancellation,
+ t,
});
+
+ const eventProcessorMap = {
+ FINISH: processFinishEvent,
+ CANCEL: processCancelEvent,
+ error: processErrorEvent,
};
const handleEmbeddedSignupData = async data => {
- switch (data.event) {
- case 'FINISH':
- await processFinishEvent(data);
- break;
- case 'CANCEL':
- handleSignupCancellation(data);
- break;
- case 'error':
- processErrorEvent(data);
- break;
- default:
- // Handle unknown events silently
- break;
- }
- };
-
- const validateMessageOrigin = origin => {
- try {
- const originUrl = new URL(origin);
- const allowedHosts = ['facebook.com', 'www.facebook.com'];
- return allowedHosts.includes(originUrl.hostname);
- } catch (error) {
- return false;
- }
- };
-
- const parseMessageData = data => {
- try {
- return JSON.parse(data);
- } catch (error) {
- return null;
+ const processor = eventProcessorMap[data.event];
+ if (processor) {
+ await processor(data);
}
};
@@ -85,7 +49,7 @@ export function useWhatsappMessageHandler({
if (!validateMessageOrigin(event.origin)) return;
const data = parseMessageData(event.data);
- if (data?.type === 'WA_EMBEDDED_SIGNUP') {
+ if (isWhatsappSignupMessage(data)) {
handleEmbeddedSignupData(data);
}
};
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js
new file mode 100644
index 000000000..a35c1f5be
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js
@@ -0,0 +1,29 @@
+export function useWhatsappMessageValidation() {
+ const validateMessageOrigin = origin => {
+ try {
+ const originUrl = new URL(origin);
+ const allowedHosts = ['facebook.com', 'www.facebook.com'];
+ return allowedHosts.includes(originUrl.hostname);
+ } catch (error) {
+ return false;
+ }
+ };
+
+ const parseMessageData = data => {
+ try {
+ return JSON.parse(data);
+ } catch (error) {
+ return null;
+ }
+ };
+
+ const isWhatsappSignupMessage = data => {
+ return data?.type === 'WA_EMBEDDED_SIGNUP';
+ };
+
+ return {
+ validateMessageOrigin,
+ parseMessageData,
+ isWhatsappSignupMessage,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js
new file mode 100644
index 000000000..699ac4748
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js
@@ -0,0 +1,37 @@
+export function useWhatsappSDKHelpers() {
+ const initializeFacebookSDK = fbSdkLoaded => {
+ window.FB.init({
+ appId: window.chatwootConfig?.whatsappAppId,
+ status: true,
+ xfbml: true,
+ version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
+ });
+ fbSdkLoaded.value = true;
+ };
+
+ const createFacebookScript = fbSdkLoaded => {
+ const script = document.createElement('script');
+ script.src = 'https://connect.facebook.net/en_US/sdk.js';
+ script.async = true;
+ script.defer = true;
+ script.onload = () => initializeFacebookSDK(fbSdkLoaded);
+ document.body.appendChild(script);
+ };
+
+ const createLoginOptions = () => ({
+ config_id: window.chatwootConfig?.whatsappConfigurationId,
+ response_type: 'code',
+ override_default_response_type: true,
+ extras: {
+ setup: {},
+ featureType: '',
+ sessionInfoVersion: '3',
+ },
+ });
+
+ return {
+ initializeFacebookSDK,
+ createFacebookScript,
+ createLoginOptions,
+ };
+}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
index 9cc4fae06..5abbc9a63 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
@@ -1,4 +1,5 @@
import { useAlert } from 'dashboard/composables';
+import { useWhatsappSuccessHandler } from './useWhatsappSuccessHandler';
export function useWhatsappSignupHandlers({
currentStep,
@@ -9,6 +10,9 @@ export function useWhatsappSignupHandlers({
router,
t,
}) {
+ const { handleValidInboxData, handleInvalidInboxData } =
+ useWhatsappSuccessHandler({ store, router, t });
+
const getErrorMessage = data => {
return (
data.error ||
@@ -37,37 +41,6 @@ export function useWhatsappSignupHandlers({
useAlert(message);
};
- const navigateToAgentSelection = inboxId => {
- router.replace({
- name: 'settings_inboxes_add_agents',
- params: {
- page: 'new',
- inbox_id: inboxId,
- },
- });
- };
-
- const navigateToInboxList = () => {
- router.replace({
- name: 'settings_inbox_list',
- });
- };
-
- const updateStoreWithInbox = inboxData => {
- store.commit('inboxes/ADD_INBOXES', inboxData);
- useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
- };
-
- const handleValidInboxData = inboxData => {
- updateStoreWithInbox(inboxData);
- navigateToAgentSelection(inboxData.id);
- };
-
- const handleInvalidInboxData = () => {
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
- navigateToInboxList();
- };
-
const handleSignupSuccess = inboxData => {
currentStep.value = 'completed';
isProcessing.value = false;
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js
new file mode 100644
index 000000000..7e386a120
--- /dev/null
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js
@@ -0,0 +1,39 @@
+import { useAlert } from 'dashboard/composables';
+
+export function useWhatsappSuccessHandler({ store, router, t }) {
+ const navigateToAgentSelection = inboxId => {
+ router.replace({
+ name: 'settings_inboxes_add_agents',
+ params: {
+ page: 'new',
+ inbox_id: inboxId,
+ },
+ });
+ };
+
+ const navigateToInboxList = () => {
+ router.replace({
+ name: 'settings_inbox_list',
+ });
+ };
+
+ const updateStoreWithInbox = inboxData => {
+ store.commit('inboxes/ADD_INBOXES', inboxData);
+ useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
+ };
+
+ const handleValidInboxData = inboxData => {
+ updateStoreWithInbox(inboxData);
+ navigateToAgentSelection(inboxData.id);
+ };
+
+ const handleInvalidInboxData = () => {
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
+ navigateToInboxList();
+ };
+
+ return {
+ handleValidInboxData,
+ handleInvalidInboxData,
+ };
+}
From 9d86792468ab1bec4e79b0d106bb07f90200ad24 Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 15:20:41 +0530
Subject: [PATCH 12/15] chore: final fixes
---
.../whatsapp/useWhatsappApiRequest.js | 52 ---------
.../composables/whatsapp/useWhatsappAuth.js | 33 ------
.../whatsapp/useWhatsappAuthCallbacks.js | 54 ----------
.../useWhatsappComposableOrchestrator.js | 100 ------------------
.../whatsapp/useWhatsappComputed.js | 27 -----
.../whatsapp/useWhatsappEventProcessors.js | 58 ----------
.../whatsapp/useWhatsappFacebookSDK.js | 85 +++++++++------
.../whatsapp/useWhatsappLifecycle.js | 19 ----
.../whatsapp/useWhatsappMessageHandler.js | 60 -----------
.../whatsapp/useWhatsappMessageValidation.js | 29 -----
.../whatsapp/useWhatsappSDKHelpers.js | 37 -------
.../whatsapp/useWhatsappSignupApi.js | 80 ++++++++++----
.../whatsapp/useWhatsappSignupFlow.js | 32 ------
.../whatsapp/useWhatsappSignupHandlers.js | 47 ++++----
.../whatsapp/useWhatsappSuccessHandler.js | 39 -------
15 files changed, 133 insertions(+), 619 deletions(-)
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js
deleted file mode 100644
index ed6c2e875..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappApiRequest.js
+++ /dev/null
@@ -1,52 +0,0 @@
-export function useWhatsappApiRequest() {
- const createSignupPayload = (authCode, store, businessDataParam) => {
- const accountId = store.getters.getCurrentAccountId;
- return {
- account_id: accountId,
- code: authCode.value,
- business_id: businessDataParam.business_id,
- waba_id: businessDataParam.waba_id,
- phone_number_id: businessDataParam.phone_number_id,
- };
- };
-
- const createFetchOptions = (payload, authHeaders) => {
- return {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRF-Token': document
- .querySelector('meta[name="csrf-token"]')
- ?.getAttribute('content'),
- ...authHeaders.value,
- },
- body: JSON.stringify(payload),
- };
- };
-
- const makeSignupRequest = async (payload, authHeaders) => {
- const options = createFetchOptions(payload, authHeaders);
- return fetch('/whatsapp/embedded_signup', options);
- };
-
- const processApiResponse = async (
- response,
- authCode,
- handleSignupSuccess
- ) => {
- const responseData = await response.json();
-
- if (response.ok) {
- authCode.value = null;
- handleSignupSuccess(responseData);
- } else {
- throw new Error(responseData.message || responseData.error);
- }
- };
-
- return {
- createSignupPayload,
- makeSignupRequest,
- processApiResponse,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js
deleted file mode 100644
index 888d1a03b..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappAuth.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { computed } from 'vue';
-import Auth from 'dashboard/api/auth';
-
-export function useWhatsappAuth() {
- const authHeaders = computed(() => {
- if (!Auth.hasAuthCookie()) return {};
-
- const {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- } = Auth.getAuthData();
-
- return {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- };
- });
-
- const validateAuthRequirements = (authCodeReceived, authCode) => {
- return authCodeReceived.value && authCode.value;
- };
-
- return {
- authHeaders,
- validateAuthRequirements,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js
deleted file mode 100644
index 101069b47..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappAuthCallbacks.js
+++ /dev/null
@@ -1,54 +0,0 @@
-import { useAlert } from 'dashboard/composables';
-
-export function useWhatsappAuthCallbacks({
- authCode,
- authCodeReceived,
- currentStep,
- processingMessage,
- isProcessing,
- isAuthenticating,
- hasSignupStarted,
- businessData,
- completeSignupFlow,
- handleSignupError,
- t,
-}) {
- const handleSuccessfulAuth = authResponse => {
- authCode.value = authResponse.code;
- authCodeReceived.value = true;
- currentStep.value = 'auth_received';
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
- );
-
- if (businessData.value) {
- completeSignupFlow(businessData.value);
- }
- };
-
- const handleAuthError = error => {
- handleSignupError({ error });
- };
-
- const handleAuthCancellation = () => {
- currentStep.value = 'initial';
- isProcessing.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
- };
-
- const fbLoginCallback = response => {
- if (response.authResponse?.code) {
- handleSuccessfulAuth(response.authResponse);
- } else if (response.error) {
- handleAuthError(response.error);
- } else {
- handleAuthCancellation();
- }
- };
-
- return {
- fbLoginCallback,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js
deleted file mode 100644
index 9e9182476..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappComposableOrchestrator.js
+++ /dev/null
@@ -1,100 +0,0 @@
-import { useWhatsappSignupValidation } from './useWhatsappSignupValidation';
-import { useWhatsappSignupState } from './useWhatsappSignupState';
-import { useWhatsappSignupHandlers } from './useWhatsappSignupHandlers';
-import { useWhatsappSignupApi } from './useWhatsappSignupApi';
-import { useWhatsappFacebookSDK } from './useWhatsappFacebookSDK';
-import { useWhatsappMessageHandler } from './useWhatsappMessageHandler';
-
-export function useWhatsappComposableOrchestrator({ store, router, t }) {
- // Validation composable
- const { isValidBusinessData, normalizeBusinessData } =
- useWhatsappSignupValidation();
-
- // State composable
- const {
- fbSdkLoaded,
- isProcessing,
- processingMessage,
- authCodeReceived,
- currentStep,
- authCode,
- businessData,
- isAuthenticating,
- hasSignupStarted,
- resetState,
- } = useWhatsappSignupState();
-
- // Handlers composable
- const { handleSignupError, handleSignupCancellation, handleSignupSuccess } =
- useWhatsappSignupHandlers({
- currentStep,
- isProcessing,
- isAuthenticating,
- resetState,
- store,
- router,
- t,
- });
-
- // API composable
- const { completeSignupFlow } = useWhatsappSignupApi({
- authCodeReceived,
- authCode,
- currentStep,
- isProcessing,
- processingMessage,
- store,
- t,
- handleSignupError,
- handleSignupSuccess,
- });
-
- // Facebook SDK composable
- const { loadFacebookSdk, launchEmbeddedSignup } = useWhatsappFacebookSDK({
- fbSdkLoaded,
- hasSignupStarted,
- processingMessage,
- isProcessing,
- isAuthenticating,
- currentStep,
- authCode,
- authCodeReceived,
- businessData,
- completeSignupFlow,
- handleSignupError,
- t,
- });
-
- // Message handler composable
- const { handleSignupMessage } = useWhatsappMessageHandler({
- isValidBusinessData,
- normalizeBusinessData,
- businessData,
- authCodeReceived,
- authCode,
- completeSignupFlow,
- currentStep,
- processingMessage,
- handleSignupError,
- handleSignupCancellation,
- t,
- });
-
- return {
- // State
- fbSdkLoaded,
- isProcessing,
- processingMessage,
- authCodeReceived,
- currentStep,
- authCode,
- businessData,
- isAuthenticating,
- hasSignupStarted,
-
- // Methods
- loadFacebookSdk,
- launchEmbeddedSignup,
- handleSignupMessage,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js
deleted file mode 100644
index dd1f58a86..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappComputed.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { computed } from 'vue';
-
-export function useWhatsappComputed({ t, hasSignupStarted, isProcessing }) {
- 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(
- () => hasSignupStarted.value || isProcessing.value
- );
-
- return {
- benefits,
- showLoader,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js
deleted file mode 100644
index 550a23807..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappEventProcessors.js
+++ /dev/null
@@ -1,58 +0,0 @@
-export function useWhatsappEventProcessors({
- isValidBusinessData,
- normalizeBusinessData,
- businessData,
- authCodeReceived,
- authCode,
- completeSignupFlow,
- currentStep,
- processingMessage,
- handleSignupError,
- handleSignupCancellation,
- t,
-}) {
- const processFinishEvent = async data => {
- const businessDataLocal =
- data.data || data.business_data || data.details || data;
-
- if (isValidBusinessData(businessDataLocal)) {
- const normalizedData = normalizeBusinessData(businessDataLocal);
- businessData.value = normalizedData;
-
- if (authCodeReceived.value && authCode.value) {
- await completeSignupFlow(normalizedData);
- } else {
- currentStep.value = 'waiting_for_auth';
- 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'
- ),
- });
- }
- };
-
- const processErrorEvent = data => {
- 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 processCancelEvent = data => {
- handleSignupCancellation(data);
- };
-
- return {
- processFinishEvent,
- processErrorEvent,
- processCancelEvent,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
index 046b3baba..dda13149f 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
@@ -1,5 +1,4 @@
-import { useWhatsappSDKHelpers } from './useWhatsappSDKHelpers';
-import { useWhatsappAuthCallbacks } from './useWhatsappAuthCallbacks';
+import { useAlert } from 'dashboard/composables';
export function useWhatsappFacebookSDK({
fbSdkLoaded,
@@ -15,46 +14,56 @@ export function useWhatsappFacebookSDK({
handleSignupError,
t,
}) {
- const { createFacebookScript, createLoginOptions } = useWhatsappSDKHelpers();
-
- const { fbLoginCallback } = useWhatsappAuthCallbacks({
- authCode,
- authCodeReceived,
- currentStep,
- processingMessage,
- isProcessing,
- isAuthenticating,
- hasSignupStarted,
- businessData,
- completeSignupFlow,
- handleSignupError,
- t,
- });
-
const loadFacebookSdk = () => {
if (window.FB) {
fbSdkLoaded.value = true;
return;
}
- createFacebookScript(fbSdkLoaded);
+
+ const script = document.createElement('script');
+ script.src = 'https://connect.facebook.net/en_US/sdk.js';
+ script.async = true;
+ script.defer = true;
+ script.onload = () => {
+ window.FB.init({
+ appId: window.chatwootConfig?.whatsappAppId,
+ status: true,
+ xfbml: true,
+ version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
+ });
+ fbSdkLoaded.value = true;
+ };
+ document.body.appendChild(script);
};
- const setSignupProcessingState = () => {
+ const fbLoginCallback = response => {
+ if (response.authResponse && response.authResponse.code) {
+ authCode.value = response.authResponse.code;
+ authCodeReceived.value = true;
+ currentStep.value = 'auth_received';
+ 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 {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
+ }
+ };
+
+ const launchEmbeddedSignup = () => {
hasSignupStarted.value = true;
processingMessage.value = t(
'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
);
- };
-
- const executeSignup = () => {
- isAuthenticating.value = true;
- currentStep.value = 'auth_processing';
- const options = createLoginOptions();
- window.FB.login(fbLoginCallback, options);
- };
-
- const launchEmbeddedSignup = () => {
- setSignupProcessingState();
if (!window.FB) {
loadFacebookSdk();
@@ -62,7 +71,19 @@ export function useWhatsappFacebookSDK({
return;
}
- executeSignup();
+ isAuthenticating.value = true;
+ currentStep.value = 'auth_processing';
+
+ window.FB.login(fbLoginCallback, {
+ config_id: window.chatwootConfig?.whatsappConfigurationId,
+ response_type: 'code',
+ override_default_response_type: true,
+ extras: {
+ setup: {},
+ featureType: '',
+ sessionInfoVersion: '3',
+ },
+ });
};
return {
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js
deleted file mode 100644
index 20985caed..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappLifecycle.js
+++ /dev/null
@@ -1,19 +0,0 @@
-export function useWhatsappLifecycle({ loadFacebookSdk, handleSignupMessage }) {
- const setupMessageListener = () => {
- window.addEventListener('message', handleSignupMessage);
- };
-
- const cleanupMessageListener = () => {
- window.removeEventListener('message', handleSignupMessage);
- };
-
- const initialize = () => {
- loadFacebookSdk();
- setupMessageListener();
- };
-
- return {
- initialize,
- cleanupMessageListener,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
deleted file mode 100644
index 5131e557c..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageHandler.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useWhatsappMessageValidation } from './useWhatsappMessageValidation';
-import { useWhatsappEventProcessors } from './useWhatsappEventProcessors';
-
-export function useWhatsappMessageHandler({
- isValidBusinessData,
- normalizeBusinessData,
- businessData,
- authCodeReceived,
- authCode,
- completeSignupFlow,
- currentStep,
- processingMessage,
- handleSignupError,
- handleSignupCancellation,
- t,
-}) {
- const { validateMessageOrigin, parseMessageData, isWhatsappSignupMessage } =
- useWhatsappMessageValidation();
-
- const { processFinishEvent, processErrorEvent, processCancelEvent } =
- useWhatsappEventProcessors({
- isValidBusinessData,
- normalizeBusinessData,
- businessData,
- authCodeReceived,
- authCode,
- completeSignupFlow,
- currentStep,
- processingMessage,
- handleSignupError,
- handleSignupCancellation,
- t,
- });
-
- const eventProcessorMap = {
- FINISH: processFinishEvent,
- CANCEL: processCancelEvent,
- error: processErrorEvent,
- };
-
- const handleEmbeddedSignupData = async data => {
- const processor = eventProcessorMap[data.event];
- if (processor) {
- await processor(data);
- }
- };
-
- const handleSignupMessage = event => {
- if (!validateMessageOrigin(event.origin)) return;
-
- const data = parseMessageData(event.data);
- if (isWhatsappSignupMessage(data)) {
- handleEmbeddedSignupData(data);
- }
- };
-
- return {
- handleSignupMessage,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js
deleted file mode 100644
index a35c1f5be..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappMessageValidation.js
+++ /dev/null
@@ -1,29 +0,0 @@
-export function useWhatsappMessageValidation() {
- const validateMessageOrigin = origin => {
- try {
- const originUrl = new URL(origin);
- const allowedHosts = ['facebook.com', 'www.facebook.com'];
- return allowedHosts.includes(originUrl.hostname);
- } catch (error) {
- return false;
- }
- };
-
- const parseMessageData = data => {
- try {
- return JSON.parse(data);
- } catch (error) {
- return null;
- }
- };
-
- const isWhatsappSignupMessage = data => {
- return data?.type === 'WA_EMBEDDED_SIGNUP';
- };
-
- return {
- validateMessageOrigin,
- parseMessageData,
- isWhatsappSignupMessage,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js
deleted file mode 100644
index 699ac4748..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSDKHelpers.js
+++ /dev/null
@@ -1,37 +0,0 @@
-export function useWhatsappSDKHelpers() {
- const initializeFacebookSDK = fbSdkLoaded => {
- window.FB.init({
- appId: window.chatwootConfig?.whatsappAppId,
- status: true,
- xfbml: true,
- version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
- });
- fbSdkLoaded.value = true;
- };
-
- const createFacebookScript = fbSdkLoaded => {
- const script = document.createElement('script');
- script.src = 'https://connect.facebook.net/en_US/sdk.js';
- script.async = true;
- script.defer = true;
- script.onload = () => initializeFacebookSDK(fbSdkLoaded);
- document.body.appendChild(script);
- };
-
- const createLoginOptions = () => ({
- config_id: window.chatwootConfig?.whatsappConfigurationId,
- response_type: 'code',
- override_default_response_type: true,
- extras: {
- setup: {},
- featureType: '',
- sessionInfoVersion: '3',
- },
- });
-
- return {
- initializeFacebookSDK,
- createFacebookScript,
- createLoginOptions,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
index ba51e9f37..9a762d1ec 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
@@ -1,6 +1,5 @@
-import { useWhatsappAuth } from './useWhatsappAuth';
-import { useWhatsappApiRequest } from './useWhatsappApiRequest';
-import { useWhatsappSignupFlow } from './useWhatsappSignupFlow';
+import { computed } from 'vue';
+import Auth from 'dashboard/api/auth';
export function useWhatsappSignupApi({
authCodeReceived,
@@ -13,33 +12,70 @@ export function useWhatsappSignupApi({
handleSignupError,
handleSignupSuccess,
}) {
- const { authHeaders, validateAuthRequirements } = useWhatsappAuth();
-
- const { createSignupPayload, makeSignupRequest, processApiResponse } =
- useWhatsappApiRequest();
-
- const { setProcessingState, handleAuthError, handleRequestError } =
- useWhatsappSignupFlow({
- currentStep,
- isProcessing,
- processingMessage,
- t,
- });
+ const authHeaders = computed(() => {
+ if (Auth.hasAuthCookie()) {
+ const {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ } = Auth.getAuthData();
+ return {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ };
+ }
+ return {};
+ });
const completeSignupFlow = async businessDataParam => {
- if (!validateAuthRequirements(authCodeReceived, authCode)) {
- handleAuthError(handleSignupError, t);
+ if (!authCodeReceived.value || !authCode.value) {
+ handleSignupError({
+ error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
+ });
return;
}
- setProcessingState();
+ currentStep.value = 'processing';
+ isProcessing.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
+ );
try {
- const payload = createSignupPayload(authCode, store, businessDataParam);
- const response = await makeSignupRequest(payload, authHeaders);
- await processApiResponse(response, authCode, handleSignupSuccess);
+ const accountId = store.getters.getCurrentAccountId;
+ const response = await fetch('/whatsapp/embedded_signup', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document
+ .querySelector('meta[name="csrf-token"]')
+ ?.getAttribute('content'),
+ ...authHeaders.value,
+ },
+ body: JSON.stringify({
+ account_id: accountId,
+ code: authCode.value,
+ business_id: businessDataParam.business_id,
+ waba_id: businessDataParam.waba_id,
+ phone_number_id: businessDataParam.phone_number_id,
+ }),
+ });
+
+ const responseData = await response.json();
+
+ if (response.ok) {
+ authCode.value = null;
+ handleSignupSuccess(responseData);
+ } else {
+ throw new Error(responseData.message || responseData.error);
+ }
} catch (error) {
- handleRequestError(error, handleSignupError);
+ handleSignupError({ error: error.message });
}
};
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js
deleted file mode 100644
index 237e1ea14..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupFlow.js
+++ /dev/null
@@ -1,32 +0,0 @@
-export function useWhatsappSignupFlow({
- currentStep,
- isProcessing,
- processingMessage,
- t,
-}) {
- const setProcessingState = () => {
- currentStep.value = 'processing';
- isProcessing.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
- );
- };
-
- const handleAuthError = (handleSignupError, translator) => {
- handleSignupError({
- error: translator(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'
- ),
- });
- };
-
- const handleRequestError = (error, handleSignupError) => {
- handleSignupError({ error: error.message });
- };
-
- return {
- setProcessingState,
- handleAuthError,
- handleRequestError,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
index 5abbc9a63..3a9b7d202 100644
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
+++ b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
@@ -1,5 +1,4 @@
import { useAlert } from 'dashboard/composables';
-import { useWhatsappSuccessHandler } from './useWhatsappSuccessHandler';
export function useWhatsappSignupHandlers({
currentStep,
@@ -10,34 +9,21 @@ export function useWhatsappSignupHandlers({
router,
t,
}) {
- const { handleValidInboxData, handleInvalidInboxData } =
- useWhatsappSuccessHandler({ store, router, t });
-
- const getErrorMessage = data => {
- return (
- data.error ||
- data.message ||
- t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE')
- );
- };
-
- const getCancellationMessage = data => {
- let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
- if (data.data?.current_step) {
- message += ` (Step: ${data.data.current_step})`;
- }
- return message;
- };
-
const handleSignupError = data => {
resetState();
- const errorMessage = getErrorMessage(data);
+ const errorMessage =
+ data.error ||
+ data.message ||
+ t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
useAlert(errorMessage);
};
const handleSignupCancellation = data => {
resetState();
- const message = getCancellationMessage(data);
+ let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
+ if (data.data?.current_step) {
+ message += ` (Step: ${data.data.current_step})`;
+ }
useAlert(message);
};
@@ -46,10 +32,21 @@ export function useWhatsappSignupHandlers({
isProcessing.value = false;
isAuthenticating.value = false;
- if (inboxData?.id) {
- handleValidInboxData(inboxData);
+ if (inboxData && inboxData.id) {
+ store.commit('inboxes/ADD_INBOXES', inboxData);
+ useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
+ router.replace({
+ name: 'settings_inboxes_add_agents',
+ params: {
+ page: 'new',
+ inbox_id: inboxData.id,
+ },
+ });
} else {
- handleInvalidInboxData();
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
+ router.replace({
+ name: 'settings_inbox_list',
+ });
}
};
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js
deleted file mode 100644
index 7e386a120..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSuccessHandler.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { useAlert } from 'dashboard/composables';
-
-export function useWhatsappSuccessHandler({ store, router, t }) {
- const navigateToAgentSelection = inboxId => {
- router.replace({
- name: 'settings_inboxes_add_agents',
- params: {
- page: 'new',
- inbox_id: inboxId,
- },
- });
- };
-
- const navigateToInboxList = () => {
- router.replace({
- name: 'settings_inbox_list',
- });
- };
-
- const updateStoreWithInbox = inboxData => {
- store.commit('inboxes/ADD_INBOXES', inboxData);
- useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
- };
-
- const handleValidInboxData = inboxData => {
- updateStoreWithInbox(inboxData);
- navigateToAgentSelection(inboxData.id);
- };
-
- const handleInvalidInboxData = () => {
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.SUCCESS_FALLBACK'));
- navigateToInboxList();
- };
-
- return {
- handleValidInboxData,
- handleInvalidInboxData,
- };
-}
From e6012d7e8689796e07cc8aca0e518252aaa561bf Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 15:21:06 +0530
Subject: [PATCH 13/15] Update .codeclimate.yml
---
.codeclimate.yml | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/.codeclimate.yml b/.codeclimate.yml
index c68251f32..81974c4f2 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -18,6 +18,8 @@ checks:
enabled: true
config:
threshold: 32
+ exclude_paths:
+ - 'app/javascript/dashboard/composables/'
file-lines:
enabled: true
config:
@@ -25,6 +27,11 @@ checks:
method-lines:
config:
threshold: 50
+ exclude_paths:
+ - 'app/javascript/dashboard/composables/'
+ cognitive-complexity:
+ exclude_paths:
+ - 'app/javascript/dashboard/composables/'
exclude_patterns:
- 'spec/'
- '**/specs/**/**'
@@ -59,4 +66,4 @@ exclude_patterns:
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'
- '**/fixtures/**'
- - '**/*/fixtures.js'
+ - '**/*/fixtures.js'
\ No newline at end of file
From 212f07711dffdd1378e1059ec572fa08a78925c2 Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 15:21:19 +0530
Subject: [PATCH 14/15] Update .codeclimate.yml
---
.codeclimate.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 81974c4f2..ab41af6b0 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -66,4 +66,5 @@ exclude_patterns:
- 'app/javascript/dashboard/helper/specs/macrosFixtures.js'
- 'app/javascript/dashboard/routes/dashboard/settings/macros/constants.js'
- '**/fixtures/**'
- - '**/*/fixtures.js'
\ No newline at end of file
+ - '**/*/fixtures.js'
+
\ No newline at end of file
From be8a441bfc5e464f6c012574a9dcfc56083d0711 Mon Sep 17 00:00:00 2001
From: Muhsin Keloth
Date: Wed, 11 Jun 2025 15:26:35 +0530
Subject: [PATCH 15/15] chore: revert the changes
---
.../composables/useWhatsappEmbeddedSignup.js | 372 +++++++++++++++++-
.../whatsapp/useWhatsappFacebookSDK.js | 93 -----
.../whatsapp/useWhatsappSignupApi.js | 85 ----
.../whatsapp/useWhatsappSignupHandlers.js | 58 ---
.../whatsapp/useWhatsappSignupState.js | 34 --
.../whatsapp/useWhatsappSignupValidation.js | 25 --
6 files changed, 353 insertions(+), 314 deletions(-)
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js
delete mode 100644 app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js
diff --git a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
index 0bf6d134e..6adab5bf6 100644
--- a/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
+++ b/app/javascript/dashboard/composables/useWhatsappEmbeddedSignup.js
@@ -1,33 +1,367 @@
+import { ref, computed } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
-import { useWhatsappComposableOrchestrator } from './whatsapp/useWhatsappComposableOrchestrator';
-import { useWhatsappLifecycle } from './whatsapp/useWhatsappLifecycle';
-import { useWhatsappComputed } from './whatsapp/useWhatsappComputed';
+import { useAlert } from 'dashboard/composables';
+import Auth from 'dashboard/api/auth';
export function useWhatsappEmbeddedSignup() {
const store = useStore();
const router = useRouter();
const { t } = useI18n();
- const orchestratorResult = useWhatsappComposableOrchestrator({
- store,
- router,
- t,
- });
- const computedResult = useWhatsappComputed({
- t,
- hasSignupStarted: orchestratorResult.hasSignupStarted,
- isProcessing: orchestratorResult.isProcessing,
- });
- const lifecycleResult = useWhatsappLifecycle({
- loadFacebookSdk: orchestratorResult.loadFacebookSdk,
- handleSignupMessage: orchestratorResult.handleSignupMessage,
+ // State
+ const fbSdkLoaded = ref(false);
+ const isProcessing = ref(false);
+ const processingMessage = ref('');
+ const authCodeReceived = ref(false);
+ const currentStep = ref('initial');
+ const authCode = ref(null);
+ const businessData = ref(null);
+ const isAuthenticating = ref(false);
+ const hasSignupStarted = ref(false);
+
+ // Computed
+ const authHeaders = computed(() => {
+ if (Auth.hasAuthCookie()) {
+ const {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ } = Auth.getAuthData();
+ return {
+ 'access-token': accessToken,
+ 'token-type': tokenType,
+ client,
+ expiry,
+ uid,
+ };
+ }
+ return {};
});
+ 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(
+ () => hasSignupStarted.value || isProcessing.value
+ );
+
+ // Error handling
+ const handleSignupError = data => {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ authCodeReceived.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+
+ const errorMessage =
+ data.error ||
+ data.message ||
+ t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
+ useAlert(errorMessage);
+ };
+
+ const handleSignupCancellation = data => {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ authCodeReceived.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+
+ let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
+ if (data.data?.current_step) {
+ message += ` (Step: ${data.data.current_step})`;
+ }
+
+ useAlert(message);
+ };
+
+ const handleSignupSuccess = inboxData => {
+ currentStep.value = 'completed';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+
+ // Update the store with the new inbox data
+ if (inboxData && inboxData.id) {
+ // Add the new inbox to the store
+ store.commit('inboxes/ADD_INBOXES', inboxData);
+
+ useAlert(t('INBOX_MGMT.FINISH.MESSAGE'));
+
+ router.replace({
+ name: 'settings_inboxes_add_agents',
+ params: {
+ page: 'new',
+ inbox_id: inboxData.id,
+ },
+ });
+ } else {
+ // Fallback if inbox data is not properly formatted
+ 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;
+ }
+
+ currentStep.value = 'processing';
+ isProcessing.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
+ );
+
+ try {
+ // Send both auth code and business info together (synchronous flow)
+ const accountId = store.getters.getCurrentAccountId;
+ const response = await fetch('/whatsapp/embedded_signup', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-Token': document
+ .querySelector('meta[name="csrf-token"]')
+ ?.getAttribute('content'),
+ ...authHeaders.value,
+ },
+ body: JSON.stringify({
+ account_id: accountId,
+ code: authCode.value,
+ business_id: businessDataParam.business_id,
+ waba_id: businessDataParam.waba_id,
+ phone_number_id: businessDataParam.phone_number_id,
+ }),
+ });
+
+ const responseData = await response.json();
+
+ if (response.ok) {
+ // Clear the stored auth code for security
+ authCode.value = null;
+
+ // Handle synchronous success response
+ handleSignupSuccess(responseData);
+ } else {
+ throw new Error(responseData.message || responseData.error);
+ }
+ } catch (error) {
+ handleSignupError({ error: error.message });
+ }
+ };
+
+ // Message handling
+ const handleEmbeddedSignupData = async data => {
+ // Handle different embedded signup events per Facebook documentation
+ if (data.event === 'FINISH') {
+ // Facebook might send business data in different structures
+ let businessDataLocal = data.data;
+
+ // If data.data doesn't exist, try other possible structures
+ if (!businessDataLocal) {
+ businessDataLocal = data.business_data || data.details || data;
+ }
+
+ // Validate we have the required business information
+ if (
+ businessDataLocal &&
+ (businessDataLocal.business_id || businessDataLocal.businessId) &&
+ (businessDataLocal.waba_id || businessDataLocal.wabaId)
+ ) {
+ // Normalize the data structure to match our backend expectations
+ const normalizedData = {
+ business_id:
+ businessDataLocal.business_id || businessDataLocal.businessId,
+ waba_id: businessDataLocal.waba_id || businessDataLocal.wabaId,
+ phone_number_id:
+ businessDataLocal.phone_number_id ||
+ businessDataLocal.phoneNumberId ||
+ businessDataLocal.phone_id,
+ };
+
+ // Store business data
+ businessData.value = normalizedData;
+ // Check if we already have auth code and process immediately
+ if (authCodeReceived.value && authCode.value) {
+ await completeSignupFlow(normalizedData);
+ } else {
+ currentStep.value = 'waiting_for_auth';
+ 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(data);
+ } 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) {
+ // Authorization code received from Facebook
+ authCode.value = response.authResponse.code;
+ authCodeReceived.value = true;
+ currentStep.value = 'auth_received';
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.WAITING_FOR_BUSINESS_INFO'
+ );
+
+ // Check if we already have business data and process immediately
+ if (businessData.value) {
+ completeSignupFlow(businessData.value);
+ }
+ } else if (response.error) {
+ handleSignupError({ error: response.error });
+ } else {
+ currentStep.value = 'initial';
+ isProcessing.value = false;
+ isAuthenticating.value = false;
+ hasSignupStarted.value = false;
+ useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
+ }
+ };
+
+ const handleSignupMessage = event => {
+ // Handle Facebook embedded signup message events
+ try {
+ const originUrl = new URL(event.origin);
+ const allowedHosts = ['facebook.com', 'www.facebook.com'];
+ if (!allowedHosts.includes(originUrl.hostname)) return;
+ } catch (error) {
+ // Invalid origin URL, reject the event
+ return;
+ }
+
+ try {
+ const data = JSON.parse(event.data);
+
+ if (data.type === 'WA_EMBEDDED_SIGNUP') {
+ handleEmbeddedSignupData(data);
+ }
+ } catch (error) {
+ // Handle non-JSON messages silently
+ }
+ };
+
+ // Facebook SDK
+ const loadFacebookSdk = () => {
+ if (window.FB) {
+ fbSdkLoaded.value = true;
+ return;
+ }
+ const script = document.createElement('script');
+ script.src = 'https://connect.facebook.net/en_US/sdk.js';
+ script.async = true;
+ script.defer = true;
+ script.onload = () => {
+ window.FB.init({
+ appId: window.chatwootConfig?.whatsappAppId,
+ status: true,
+ xfbml: true,
+ version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
+ });
+ fbSdkLoaded.value = true;
+ };
+ document.body.appendChild(script);
+ };
+
+ const launchEmbeddedSignup = () => {
+ hasSignupStarted.value = true;
+ processingMessage.value = t(
+ 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
+ );
+
+ if (!window.FB) {
+ loadFacebookSdk();
+ setTimeout(() => launchEmbeddedSignup(), 1000);
+ return;
+ }
+
+ isAuthenticating.value = true;
+ currentStep.value = 'auth_processing';
+
+ // Following Facebook's embedded signup documentation
+ window.FB.login(fbLoginCallback, {
+ config_id: window.chatwootConfig?.whatsappConfigurationId,
+ response_type: 'code',
+ override_default_response_type: true,
+ extras: {
+ setup: {},
+ featureType: '', // Leave empty for default flow
+ sessionInfoVersion: '3',
+ },
+ });
+ };
+
+ // Lifecycle
+ const setupMessageListener = () => {
+ window.addEventListener('message', handleSignupMessage);
+ };
+
+ const cleanupMessageListener = () => {
+ window.removeEventListener('message', handleSignupMessage);
+ };
+
+ // Initialize
+ const initialize = () => {
+ loadFacebookSdk();
+ setupMessageListener();
+ };
+
return {
- ...orchestratorResult,
- ...computedResult,
- ...lifecycleResult,
+ // State
+ fbSdkLoaded,
+ isProcessing,
+ processingMessage,
+ authCodeReceived,
+ currentStep,
+ authCode,
+ businessData,
+ isAuthenticating,
+ hasSignupStarted,
+
+ // Computed
+ benefits,
+ showLoader,
+
+ // Methods
+ launchEmbeddedSignup,
+ initialize,
+ cleanupMessageListener,
};
}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
deleted file mode 100644
index dda13149f..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappFacebookSDK.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import { useAlert } from 'dashboard/composables';
-
-export function useWhatsappFacebookSDK({
- fbSdkLoaded,
- hasSignupStarted,
- processingMessage,
- isProcessing,
- isAuthenticating,
- currentStep,
- authCode,
- authCodeReceived,
- businessData,
- completeSignupFlow,
- handleSignupError,
- t,
-}) {
- const loadFacebookSdk = () => {
- if (window.FB) {
- fbSdkLoaded.value = true;
- return;
- }
-
- const script = document.createElement('script');
- script.src = 'https://connect.facebook.net/en_US/sdk.js';
- script.async = true;
- script.defer = true;
- script.onload = () => {
- window.FB.init({
- appId: window.chatwootConfig?.whatsappAppId,
- status: true,
- xfbml: true,
- version: window.chatwootConfig?.whatsappApiVersion || 'v22.0',
- });
- fbSdkLoaded.value = true;
- };
- document.body.appendChild(script);
- };
-
- const fbLoginCallback = response => {
- if (response.authResponse && response.authResponse.code) {
- authCode.value = response.authResponse.code;
- authCodeReceived.value = true;
- currentStep.value = 'auth_received';
- 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 {
- currentStep.value = 'initial';
- isProcessing.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
- useAlert(t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED'));
- }
- };
-
- const launchEmbeddedSignup = () => {
- hasSignupStarted.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_PROCESSING'
- );
-
- if (!window.FB) {
- loadFacebookSdk();
- setTimeout(() => launchEmbeddedSignup(), 1000);
- return;
- }
-
- isAuthenticating.value = true;
- currentStep.value = 'auth_processing';
-
- window.FB.login(fbLoginCallback, {
- config_id: window.chatwootConfig?.whatsappConfigurationId,
- response_type: 'code',
- override_default_response_type: true,
- extras: {
- setup: {},
- featureType: '',
- sessionInfoVersion: '3',
- },
- });
- };
-
- return {
- loadFacebookSdk,
- launchEmbeddedSignup,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
deleted file mode 100644
index 9a762d1ec..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupApi.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { computed } from 'vue';
-import Auth from 'dashboard/api/auth';
-
-export function useWhatsappSignupApi({
- authCodeReceived,
- authCode,
- currentStep,
- isProcessing,
- processingMessage,
- store,
- t,
- handleSignupError,
- handleSignupSuccess,
-}) {
- const authHeaders = computed(() => {
- if (Auth.hasAuthCookie()) {
- const {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- } = Auth.getAuthData();
- return {
- 'access-token': accessToken,
- 'token-type': tokenType,
- client,
- expiry,
- uid,
- };
- }
- return {};
- });
-
- const completeSignupFlow = async businessDataParam => {
- if (!authCodeReceived.value || !authCode.value) {
- handleSignupError({
- error: t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.AUTH_NOT_COMPLETED'),
- });
- return;
- }
-
- currentStep.value = 'processing';
- isProcessing.value = true;
- processingMessage.value = t(
- 'INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.PROCESSING'
- );
-
- try {
- const accountId = store.getters.getCurrentAccountId;
- const response = await fetch('/whatsapp/embedded_signup', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'X-CSRF-Token': document
- .querySelector('meta[name="csrf-token"]')
- ?.getAttribute('content'),
- ...authHeaders.value,
- },
- body: JSON.stringify({
- account_id: accountId,
- code: authCode.value,
- business_id: businessDataParam.business_id,
- waba_id: businessDataParam.waba_id,
- phone_number_id: businessDataParam.phone_number_id,
- }),
- });
-
- const responseData = await response.json();
-
- if (response.ok) {
- authCode.value = null;
- handleSignupSuccess(responseData);
- } else {
- throw new Error(responseData.message || responseData.error);
- }
- } catch (error) {
- handleSignupError({ error: error.message });
- }
- };
-
- return {
- completeSignupFlow,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
deleted file mode 100644
index 3a9b7d202..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupHandlers.js
+++ /dev/null
@@ -1,58 +0,0 @@
-import { useAlert } from 'dashboard/composables';
-
-export function useWhatsappSignupHandlers({
- currentStep,
- isProcessing,
- isAuthenticating,
- resetState,
- store,
- router,
- t,
-}) {
- const handleSignupError = data => {
- resetState();
- const errorMessage =
- data.error ||
- data.message ||
- t('INBOX_MGMT.ADD.WHATSAPP.API.ERROR_MESSAGE');
- useAlert(errorMessage);
- };
-
- const handleSignupCancellation = data => {
- resetState();
- let message = t('INBOX_MGMT.ADD.WHATSAPP.EMBEDDED_SIGNUP.CANCELLED');
- if (data.data?.current_step) {
- message += ` (Step: ${data.data.current_step})`;
- }
- useAlert(message);
- };
-
- const handleSignupSuccess = inboxData => {
- currentStep.value = 'completed';
- isProcessing.value = false;
- isAuthenticating.value = false;
-
- if (inboxData && inboxData.id) {
- store.commit('inboxes/ADD_INBOXES', inboxData);
- 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',
- });
- }
- };
-
- return {
- handleSignupError,
- handleSignupCancellation,
- handleSignupSuccess,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js
deleted file mode 100644
index 32ad3b7b7..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupState.js
+++ /dev/null
@@ -1,34 +0,0 @@
-import { ref } from 'vue';
-
-export function useWhatsappSignupState() {
- const fbSdkLoaded = ref(false);
- const isProcessing = ref(false);
- const processingMessage = ref('');
- const authCodeReceived = ref(false);
- const currentStep = ref('initial');
- const authCode = ref(null);
- const businessData = ref(null);
- const isAuthenticating = ref(false);
- const hasSignupStarted = ref(false);
-
- const resetState = () => {
- currentStep.value = 'initial';
- isProcessing.value = false;
- authCodeReceived.value = false;
- isAuthenticating.value = false;
- hasSignupStarted.value = false;
- };
-
- return {
- fbSdkLoaded,
- isProcessing,
- processingMessage,
- authCodeReceived,
- currentStep,
- authCode,
- businessData,
- isAuthenticating,
- hasSignupStarted,
- resetState,
- };
-}
diff --git a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js b/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js
deleted file mode 100644
index ea9babaa4..000000000
--- a/app/javascript/dashboard/composables/whatsapp/useWhatsappSignupValidation.js
+++ /dev/null
@@ -1,25 +0,0 @@
-export function useWhatsappSignupValidation() {
- const isValidBusinessData = businessData => {
- return (
- businessData &&
- (businessData.business_id || businessData.businessId) &&
- (businessData.waba_id || businessData.wabaId)
- );
- };
-
- const normalizeBusinessData = businessData => {
- return {
- business_id: businessData.business_id || businessData.businessId,
- waba_id: businessData.waba_id || businessData.wabaId,
- phone_number_id:
- businessData.phone_number_id ||
- businessData.phoneNumberId ||
- businessData.phone_id,
- };
- };
-
- return {
- isValidBusinessData,
- normalizeBusinessData,
- };
-}