diff --git a/.rubocop.yml b/.rubocop.yml index 12e756af6..e30a71ee9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -283,7 +283,7 @@ Rails/RedundantActiveRecordAllMethod: Enabled: false Layout/TrailingEmptyLines: - Enabled: false + Enabled: true Style/SafeNavigationChainLength: Enabled: false diff --git a/app/controllers/api/v1/accounts/campaigns_controller.rb b/app/controllers/api/v1/accounts/campaigns_controller.rb index b1a132246..2a1650e53 100644 --- a/app/controllers/api/v1/accounts/campaigns_controller.rb +++ b/app/controllers/api/v1/accounts/campaigns_controller.rb @@ -29,6 +29,6 @@ class Api::V1::Accounts::CampaignsController < Api::V1::Accounts::BaseController def campaign_params params.require(:campaign).permit(:title, :description, :message, :enabled, :trigger_only_during_business_hours, :inbox_id, :sender_id, - :scheduled_at, audience: [:type, :id], trigger_rules: {}) + :scheduled_at, audience: [:type, :id], trigger_rules: {}, template_params: {}) end end diff --git a/app/controllers/api/v1/accounts/integrations/notion_controller.rb b/app/controllers/api/v1/accounts/integrations/notion_controller.rb index dff6ccece..ecf6bae6e 100644 --- a/app/controllers/api/v1/accounts/integrations/notion_controller.rb +++ b/app/controllers/api/v1/accounts/integrations/notion_controller.rb @@ -11,4 +11,4 @@ class Api::V1::Accounts::Integrations::NotionController < Api::V1::Accounts::Bas def fetch_hook @hook = Integrations::Hook.where(account: Current.account).find_by(app_id: 'notion') end -end \ No newline at end of file +end diff --git a/app/controllers/api/v1/accounts/notion/authorizations_controller.rb b/app/controllers/api/v1/accounts/notion/authorizations_controller.rb index bb9b2f858..3e0f6586a 100644 --- a/app/controllers/api/v1/accounts/notion/authorizations_controller.rb +++ b/app/controllers/api/v1/accounts/notion/authorizations_controller.rb @@ -18,4 +18,4 @@ class Api::V1::Accounts::Notion::AuthorizationsController < Api::V1::Accounts::O render json: { success: false }, status: :unprocessable_entity end end -end \ No newline at end of file +end diff --git a/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb b/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb new file mode 100644 index 000000000..e7a1f3fa6 --- /dev/null +++ b/app/controllers/api/v1/accounts/whatsapp/authorizations_controller.rb @@ -0,0 +1,64 @@ +class Api::V1::Accounts::Whatsapp::AuthorizationsController < Api::V1::Accounts::BaseController + before_action :validate_feature_enabled! + + # POST /api/v1/accounts/:account_id/whatsapp/authorization + # Handles the embedded signup callback data from the Facebook SDK + def create + validate_embedded_signup_params! + channel = process_embedded_signup + render_success_response(channel.inbox) + rescue StandardError => e + render_error_response(e) + end + + private + + def process_embedded_signup + service = Whatsapp::EmbeddedSignupService.new( + account: Current.account, + code: params[:code], + business_id: params[:business_id], + waba_id: params[:waba_id], + phone_number_id: params[:phone_number_id] + ) + service.perform + end + + def render_success_response(inbox) + render json: { + success: true, + id: inbox.id, + name: inbox.name, + channel_type: 'whatsapp' + } + end + + def render_error_response(error) + Rails.logger.error "[WHATSAPP AUTHORIZATION] Embedded signup error: #{error.message}" + Rails.logger.error error.backtrace.join("\n") + render json: { + success: false, + error: error.message + }, status: :unprocessable_entity + end + + def validate_feature_enabled! + return if Current.account.feature_whatsapp_embedded_signup? + + render json: { + success: false, + error: 'WhatsApp embedded signup is not enabled for this account' + }, status: :forbidden + end + + def validate_embedded_signup_params! + missing_params = [] + missing_params << 'code' if params[:code].blank? + missing_params << 'business_id' if params[:business_id].blank? + missing_params << 'waba_id' if params[:waba_id].blank? + + return if missing_params.empty? + + raise ArgumentError, "Required parameters are missing: #{missing_params.join(', ')}" + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 6a4ce2461..4a2df5ee5 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -67,6 +67,8 @@ class DashboardController < ActionController::Base FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''), INSTAGRAM_APP_ID: GlobalConfigService.load('INSTAGRAM_APP_ID', ''), FACEBOOK_API_VERSION: GlobalConfigService.load('FACEBOOK_API_VERSION', 'v17.0'), + WHATSAPP_APP_ID: GlobalConfigService.load('WHATSAPP_APP_ID', ''), + WHATSAPP_CONFIGURATION_ID: GlobalConfigService.load('WHATSAPP_CONFIGURATION_ID', ''), IS_ENTERPRISE: ChatwootApp.enterprise?, AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''), GIT_SHA: GIT_HASH diff --git a/app/controllers/notion/callbacks_controller.rb b/app/controllers/notion/callbacks_controller.rb index 94030fc8e..22dcf6d30 100644 --- a/app/controllers/notion/callbacks_controller.rb +++ b/app/controllers/notion/callbacks_controller.rb @@ -33,4 +33,4 @@ class Notion::CallbacksController < OauthCallbackController def notion_redirect_uri "#{ENV.fetch('FRONTEND_URL', nil)}/app/accounts/#{account.id}/settings/integrations/notion" end -end \ No newline at end of file +end diff --git a/app/controllers/super_admin/application_controller.rb b/app/controllers/super_admin/application_controller.rb index 775fb34fc..5b04fbb44 100644 --- a/app/controllers/super_admin/application_controller.rb +++ b/app/controllers/super_admin/application_controller.rb @@ -7,8 +7,9 @@ class SuperAdmin::ApplicationController < Administrate::ApplicationController include ActionView::Helpers::TagHelper include ActionView::Context + include SuperAdmin::NavigationHelper - helper_method :render_vue_component + helper_method :render_vue_component, :settings_open?, :settings_pages # authenticiation done via devise : SuperAdmin Model before_action :authenticate_super_admin! diff --git a/app/helpers/super_admin/features.yml b/app/helpers/super_admin/features.yml new file mode 100644 index 000000000..f49004e79 --- /dev/null +++ b/app/helpers/super_admin/features.yml @@ -0,0 +1,134 @@ +# TODO: Move this values to features.yml itself +# No need to replicate the same values in two places + +# ------- Premium Features ------- # +captain: + name: 'Captain' + description: 'Enable AI-powered conversations with your customers.' + enabled: <%= (ChatwootHub.pricing_plan != 'community') %> + icon: 'icon-captain' + config_key: 'captain' + enterprise: true +custom_branding: + name: 'Custom Branding' + description: 'Apply your own branding to this installation.' + enabled: <%= (ChatwootHub.pricing_plan != 'community') %> + icon: 'icon-paint-brush-line' + config_key: 'custom_branding' + enterprise: true +agent_capacity: + name: 'Agent Capacity' + description: 'Set limits to auto-assigning conversations to your agents.' + enabled: <%= (ChatwootHub.pricing_plan != 'community') %> + icon: 'icon-hourglass-line' + enterprise: true +audit_logs: + name: 'Audit Logs' + description: 'Track and trace account activities with ease with detailed audit logs.' + enabled: <%= (ChatwootHub.pricing_plan != 'community') %> + icon: 'icon-menu-search-line' + enterprise: true +disable_branding: + name: 'Disable Branding' + description: 'Disable branding on live-chat widget and external emails.' + enabled: <%= (ChatwootHub.pricing_plan != 'community') %> + icon: 'icon-sailbot-fill' + enterprise: true + +# ------- Product Features ------- # +help_center: + name: 'Help Center' + description: 'Allow agents to create help center articles and publish them in a portal.' + enabled: true + icon: 'icon-book-2-line' + +# ------- Communication Channels ------- # +live_chat: + name: 'Live Chat' + description: 'Improve your customer experience using a live chat on your website.' + enabled: true + icon: 'icon-chat-smile-3-line' +email: + name: 'Email' + description: 'Manage your email customer interactions from Chatwoot.' + enabled: true + icon: 'icon-mail-send-fill' + config_key: 'email' +sms: + name: 'SMS' + description: 'Manage your SMS customer interactions from Chatwoot.' + enabled: true + icon: 'icon-message-line' +messenger: + name: 'Messenger' + description: 'Stay connected with your customers on Facebook & Instagram.' + enabled: true + icon: 'icon-messenger-line' + config_key: 'facebook' +instagram: + name: 'Instagram' + description: 'Stay connected with your customers on Instagram' + enabled: true + icon: 'icon-instagram' + config_key: 'instagram' +whatsapp: + name: 'WhatsApp' + description: 'Manage your WhatsApp business interactions from Chatwoot.' + enabled: true + icon: 'icon-whatsapp-line' +telegram: + name: 'Telegram' + description: 'Manage your Telegram customer interactions from Chatwoot.' + enabled: true + icon: 'icon-telegram-line' +line: + name: 'Line' + description: 'Manage your Line customer interactions from Chatwoot.' + enabled: true + icon: 'icon-line-line' + +# ------- OAuth & Authentication ------- # +google: + name: 'Google' + description: 'Configuration for setting up Google OAuth Integration' + enabled: true + icon: 'icon-google' + config_key: 'google' +microsoft: + name: 'Microsoft' + description: 'Configuration for setting up Microsoft Email' + enabled: true + icon: 'icon-microsoft' + config_key: 'microsoft' + +# ------- Third-party Integrations ------- # +linear: + name: 'Linear' + description: 'Configuration for setting up Linear Integration' + enabled: true + icon: 'icon-linear' + config_key: 'linear' +notion: + name: 'Notion' + description: 'Configuration for setting up Notion Integration' + enabled: true + icon: 'icon-notion' + config_key: 'notion' +slack: + name: 'Slack' + description: 'Configuration for setting up Slack Integration' + enabled: true + icon: 'icon-slack' + config_key: 'slack' +whatsapp_embedded: + name: 'WhatsApp Embedded' + description: 'Configuration for setting up WhatsApp Embedded Integration' + enabled: true + icon: 'icon-whatsapp-line' + config_key: 'whatsapp_embedded' +shopify: + name: 'Shopify' + description: 'Configuration for setting up Shopify Integration' + enabled: true + icon: 'icon-shopify' + config_key: 'shopify' diff --git a/enterprise/app/helpers/super_admin/features_helper.rb b/app/helpers/super_admin/features_helper.rb similarity index 78% rename from enterprise/app/helpers/super_admin/features_helper.rb rename to app/helpers/super_admin/features_helper.rb index 2fbcd1715..475ad6d25 100644 --- a/enterprise/app/helpers/super_admin/features_helper.rb +++ b/app/helpers/super_admin/features_helper.rb @@ -1,6 +1,6 @@ module SuperAdmin::FeaturesHelper def self.available_features - YAML.load(ERB.new(Rails.root.join('enterprise/app/helpers/super_admin/features.yml').read).result).with_indifferent_access + YAML.load(ERB.new(Rails.root.join('app/helpers/super_admin/features.yml').read).result).with_indifferent_access end def self.plan_details diff --git a/app/helpers/super_admin/navigation_helper.rb b/app/helpers/super_admin/navigation_helper.rb new file mode 100644 index 000000000..5fca3fa76 --- /dev/null +++ b/app/helpers/super_admin/navigation_helper.rb @@ -0,0 +1,16 @@ +module SuperAdmin::NavigationHelper + def settings_open? + params[:controller].in? %w[super_admin/settings super_admin/app_configs] + end + + def settings_pages + features = SuperAdmin::FeaturesHelper.available_features.select do |_feature, attrs| + attrs['config_key'].present? && attrs['enabled'] + end + + # Add general at the beginning + general_feature = [['general', { 'config_key' => 'general', 'name' => 'General' }]] + + general_feature + features.to_a + end +end diff --git a/app/javascript/dashboard/api/channel/whatsappChannel.js b/app/javascript/dashboard/api/channel/whatsappChannel.js new file mode 100644 index 000000000..e1003b123 --- /dev/null +++ b/app/javascript/dashboard/api/channel/whatsappChannel.js @@ -0,0 +1,14 @@ +/* global axios */ +import ApiClient from '../ApiClient'; + +class WhatsappChannel extends ApiClient { + constructor() { + super('whatsapp', { accountScoped: true }); + } + + createEmbeddedSignup(params) { + return axios.post(`${this.baseUrl()}/whatsapp/authorization`, params); + } +} + +export default new WhatsappChannel(); diff --git a/app/javascript/dashboard/components-next/Campaigns/EmptyState/WhatsAppCampaignEmptyState.vue b/app/javascript/dashboard/components-next/Campaigns/EmptyState/WhatsAppCampaignEmptyState.vue new file mode 100644 index 000000000..ab01acb4a --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/EmptyState/WhatsAppCampaignEmptyState.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignDialog.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignDialog.vue new file mode 100644 index 000000000..12a789fee --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignDialog.vue @@ -0,0 +1,48 @@ + + + diff --git a/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue new file mode 100644 index 000000000..df76ae901 --- /dev/null +++ b/app/javascript/dashboard/components-next/Campaigns/Pages/CampaignPage/WhatsAppCampaign/WhatsAppCampaignForm.vue @@ -0,0 +1,357 @@ + + + diff --git a/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue b/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue index 4a48794b3..aca5ce7b8 100644 --- a/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue +++ b/app/javascript/dashboard/components-next/captain/SettingsPageLayout.vue @@ -52,9 +52,9 @@ const handleBreadcrumbClick = item => {