diff --git a/app/controllers/api/v1/accounts/inbox_csat_templates_controller.rb b/app/controllers/api/v1/accounts/inbox_csat_templates_controller.rb index d17fe35fb..bb5dab680 100644 --- a/app/controllers/api/v1/accounts/inbox_csat_templates_controller.rb +++ b/app/controllers/api/v1/accounts/inbox_csat_templates_controller.rb @@ -1,38 +1,27 @@ class Api::V1::Accounts::InboxCsatTemplatesController < Api::V1::Accounts::BaseController - DEFAULT_BUTTON_TEXT = 'Please rate us'.freeze - DEFAULT_LANGUAGE = 'en'.freeze - before_action :fetch_inbox before_action :validate_whatsapp_channel def show - template = @inbox.csat_config&.dig('template') - return render json: { template_exists: false } unless template + service = CsatTemplateManagementService.new(@inbox) + result = service.template_status - template_name = template['name'] || Whatsapp::CsatTemplateNameService.csat_template_name(@inbox.id) - status_result = @inbox.channel.provider_service.get_template_status(template_name) - - render_template_status_response(status_result, template_name) - rescue StandardError => e - Rails.logger.error "Error fetching CSAT template status: #{e.message}" - render json: { error: e.message }, status: :internal_server_error + if result[:service_error] + render json: { error: result[:service_error] }, status: :internal_server_error + else + render json: result + end end def create template_params = extract_template_params return render_missing_message_error if template_params[:message].blank? - # Delete existing template even though we are using a new one. - # We don't want too many templates in the business portfolio, but the create operation shouldn't fail if deletion fails. - delete_existing_template_if_needed - - result = create_template_via_provider(template_params) + service = CsatTemplateManagementService.new(@inbox) + result = service.create_template(template_params) render_template_creation_result(result) rescue ActionController::ParameterMissing render json: { error: 'Template parameters are required' }, status: :unprocessable_entity - rescue StandardError => e - Rails.logger.error "Error creating CSAT template: #{e.message}" - render json: { error: 'Template creation failed' }, status: :internal_server_error end private @@ -43,9 +32,9 @@ class Api::V1::Accounts::InboxCsatTemplatesController < Api::V1::Accounts::BaseC end def validate_whatsapp_channel - return if @inbox.whatsapp? + return if @inbox.whatsapp? || @inbox.twilio_whatsapp? - render json: { error: 'CSAT template operations only available for WhatsApp channels' }, + render json: { error: 'CSAT template operations only available for WhatsApp and Twilio WhatsApp channels' }, status: :bad_request end @@ -57,35 +46,36 @@ class Api::V1::Accounts::InboxCsatTemplatesController < Api::V1::Accounts::BaseC render json: { error: 'Message is required' }, status: :unprocessable_entity end - def create_template_via_provider(template_params) - template_config = { - message: template_params[:message], - button_text: template_params[:button_text] || DEFAULT_BUTTON_TEXT, - base_url: ENV.fetch('FRONTEND_URL', 'http://localhost:3000'), - language: template_params[:language] || DEFAULT_LANGUAGE, - template_name: Whatsapp::CsatTemplateNameService.csat_template_name(@inbox.id) - } - - @inbox.channel.provider_service.create_csat_template(template_config) - end - def render_template_creation_result(result) if result[:success] render_successful_template_creation(result) + elsif result[:service_error] + render json: { error: result[:service_error] }, status: :internal_server_error else render_failed_template_creation(result) end end def render_successful_template_creation(result) - render json: { - template: { - name: result[:template_name], - template_id: result[:template_id], - status: 'PENDING', - language: result[:language] || DEFAULT_LANGUAGE - } - }, status: :created + if @inbox.twilio_whatsapp? + render json: { + template: { + friendly_name: result[:friendly_name], + content_sid: result[:content_sid], + status: result[:status] || 'pending', + language: result[:language] || 'en' + } + }, status: :created + else + render json: { + template: { + name: result[:template_name], + template_id: result[:template_id], + status: 'PENDING', + language: result[:language] || 'en' + } + }, status: :created + end end def render_failed_template_creation(result) @@ -98,45 +88,6 @@ class Api::V1::Accounts::InboxCsatTemplatesController < Api::V1::Accounts::BaseC }, status: :unprocessable_entity end - def delete_existing_template_if_needed - template = @inbox.csat_config&.dig('template') - return true if template.blank? - - template_name = template['name'] - return true if template_name.blank? - - template_status = @inbox.channel.provider_service.get_template_status(template_name) - return true unless template_status[:success] - - deletion_result = @inbox.channel.provider_service.delete_csat_template(template_name) - if deletion_result[:success] - Rails.logger.info "Deleted existing CSAT template '#{template_name}' for inbox #{@inbox.id}" - true - else - Rails.logger.warn "Failed to delete existing CSAT template '#{template_name}' for inbox #{@inbox.id}: #{deletion_result[:response_body]}" - false - end - rescue StandardError => e - Rails.logger.error "Error during template deletion for inbox #{@inbox.id}: #{e.message}" - false - end - - def render_template_status_response(status_result, template_name) - if status_result[:success] - render json: { - template_exists: true, - template_name: template_name, - status: status_result[:template][:status], - template_id: status_result[:template][:id] - } - else - render json: { - template_exists: false, - error: 'Template not found' - } - end - end - def parse_whatsapp_error(response_body) return { user_message: nil, technical_details: nil } if response_body.blank? diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index 1c8845c04..322c7c7fe 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -176,7 +176,7 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController :lock_to_single_conversation, :portal_id, :sender_name_type, :business_name, { csat_config: [:display_type, :message, :button_text, :language, { survey_rules: [:operator, { values: [] }], - template: [:name, :template_id, :created_at, :language] }] }] + template: [:name, :template_id, :friendly_name, :content_sid, :approval_sid, :created_at, :language, :status] }] }] end def permitted_params(channel_attributes = []) diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CustomerSatisfactionPage.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CustomerSatisfactionPage.vue index 9cf9c688f..9e91f9642 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CustomerSatisfactionPage.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/settingsPage/CustomerSatisfactionPage.vue @@ -28,10 +28,15 @@ const { t } = useI18n(); const store = useStore(); const labels = useMapGetter('labels/getLabels'); -const { isAWhatsAppCloudChannel: isWhatsAppChannel } = useInbox( +const { isAWhatsAppChannel, isATwilioWhatsAppChannel } = useInbox( props.inbox?.id ); +// Computed to check if it's any type of WhatsApp channel (Cloud or Twilio) +const isAnyWhatsAppChannel = computed( + () => isAWhatsAppChannel.value || isATwilioWhatsAppChannel.value +); + const isUpdating = ref(false); const selectedLabelValues = ref([]); const currentLabel = ref(''); @@ -116,7 +121,9 @@ const templateApprovalStatus = computed(() => { // Handle existing template with status if (templateStatus.value?.template_exists && templateStatus.value.status) { - return statusMap[templateStatus.value.status] || statusMap.PENDING; + // Convert status to uppercase for consistency with statusMap keys + const normalizedStatus = templateStatus.value.status.toUpperCase(); + return statusMap[normalizedStatus] || statusMap.PENDING; } // Default case - no template exists @@ -155,7 +162,7 @@ const initializeState = () => { : []; // Store original template values for change detection - if (isWhatsAppChannel.value) { + if (isAnyWhatsAppChannel.value) { originalTemplateValues.value = { message: state.message, templateButtonText: state.templateButtonText, @@ -165,7 +172,7 @@ const initializeState = () => { }; const checkTemplateStatus = async () => { - if (!isWhatsAppChannel.value) return; + if (!isAnyWhatsAppChannel.value) return; try { templateLoading.value = true; @@ -195,7 +202,7 @@ const checkTemplateStatus = async () => { onMounted(() => { initializeState(); if (!labels.value?.length) store.dispatch('labels/get'); - if (isWhatsAppChannel.value) checkTemplateStatus(); + if (isAnyWhatsAppChannel.value) checkTemplateStatus(); }); watch(() => props.inbox, initializeState, { immediate: true }); @@ -225,7 +232,7 @@ const removeLabel = label => { // Check if template-related fields have changed const hasTemplateChanges = () => { - if (!isWhatsAppChannel.value) return false; + if (!isAnyWhatsAppChannel.value) return false; const original = originalTemplateValues.value; return ( @@ -254,10 +261,28 @@ const shouldCreateTemplate = () => { // Build template config for saving const buildTemplateConfig = () => { - if (!hasExistingTemplate()) return null; + if (!hasExistingTemplate()) { + return null; + } const { template_name, template_id, template, status } = templateStatus.value || {}; + + if (isATwilioWhatsAppChannel.value) { + // Twilio WhatsApp format - get from existing template config + const existingTemplate = props.inbox?.csat_config?.template; + + return existingTemplate + ? { + friendly_name: existingTemplate.friendly_name, + content_sid: existingTemplate.content_sid, + language: existingTemplate.language || state.templateLanguage, + status: existingTemplate.status || status, + } + : null; + } + + // WhatsApp Cloud format return { name: template_name, template_id, @@ -273,11 +298,11 @@ const updateInbox = async attributes => { ...attributes, }; - return store.dispatch('inboxes/updateInbox', payload); + await store.dispatch('inboxes/updateInbox', payload); }; const createTemplate = async () => { - if (!isWhatsAppChannel.value) return null; + if (!isAnyWhatsAppChannel.value) return null; const response = await store.dispatch('inboxes/createCSATTemplate', { inboxId: props.inbox.id, @@ -298,7 +323,7 @@ const performSave = async () => { // For WhatsApp channels, create template first if needed if ( - isWhatsAppChannel.value && + isAnyWhatsAppChannel.value && state.csatSurveyEnabled && shouldCreateTemplate() ) { @@ -326,13 +351,25 @@ const performSave = async () => { // Use new template data if created, otherwise preserve existing template information if (newTemplateData) { - csatConfig.template = { - name: newTemplateData.name, - template_id: newTemplateData.template_id, - language: newTemplateData.language, - status: newTemplateData.status, - created_at: new Date().toISOString(), - }; + if (isATwilioWhatsAppChannel.value) { + // Twilio WhatsApp template format + csatConfig.template = { + friendly_name: newTemplateData.friendly_name, + content_sid: newTemplateData.content_sid, + language: newTemplateData.language, + status: newTemplateData.status, + created_at: new Date().toISOString(), + }; + } else { + // WhatsApp Cloud template format + csatConfig.template = { + name: newTemplateData.name, + template_id: newTemplateData.template_id, + language: newTemplateData.language, + status: newTemplateData.status, + created_at: new Date().toISOString(), + }; + } } else { const templateConfig = buildTemplateConfig(); if (templateConfig) { @@ -356,8 +393,9 @@ const performSave = async () => { const saveSettings = async () => { // Check if we need to show confirmation dialog for WhatsApp template changes + // This applies to both WhatsApp Cloud and Twilio WhatsApp channels if ( - isWhatsAppChannel.value && + isAnyWhatsAppChannel.value && state.csatSurveyEnabled && hasExistingTemplate() && hasTemplateChanges() @@ -390,7 +428,7 @@ const handleConfirmTemplateUpdate = async () => {
@@ -400,7 +438,7 @@ const handleConfirmTemplateUpdate = async () => { /> -