chore: cleanup codes
This commit is contained in:
+21
-150
@@ -53,23 +53,6 @@ const buttonComponents = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
const interactiveComponents = computed(() => {
|
||||
return templateComponents.value.filter(component =>
|
||||
['LIST', 'PRODUCT', 'CATALOG'].includes(component.type)
|
||||
);
|
||||
});
|
||||
|
||||
const isInteractiveTemplate = computed(() => {
|
||||
const hasInteractiveButtons = buttonComponents.value.some(component =>
|
||||
component.buttons?.some(button =>
|
||||
['quick_reply', 'url', 'phone_number', 'catalog_browse'].includes(
|
||||
button.type
|
||||
)
|
||||
)
|
||||
);
|
||||
return hasInteractiveButtons || interactiveComponents.value.length > 0;
|
||||
});
|
||||
|
||||
const legacyParams = computed(() => {
|
||||
const params = {};
|
||||
Object.keys(processedParams.value).forEach(key => {
|
||||
@@ -256,29 +239,6 @@ const generateVariables = () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Process interactive components (LIST, PRODUCT, CATALOG)
|
||||
interactiveComponents.value.forEach(component => {
|
||||
if (component.type === 'LIST') {
|
||||
allVariables.interactive = {
|
||||
type: 'list',
|
||||
button_text: 'Select Option',
|
||||
sections: component.sections || [],
|
||||
};
|
||||
} else if (component.type === 'PRODUCT') {
|
||||
allVariables.interactive = {
|
||||
type: 'product',
|
||||
catalog_id: component.catalog_id || '',
|
||||
product_id: component.product_id || '',
|
||||
};
|
||||
} else if (component.type === 'CATALOG') {
|
||||
allVariables.interactive = {
|
||||
type: 'catalog',
|
||||
catalog_id: component.catalog_id || '',
|
||||
title: component.title || 'Browse Products',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
processedParams.value = allVariables;
|
||||
};
|
||||
|
||||
@@ -465,17 +425,18 @@ onMounted(() => {
|
||||
<div
|
||||
class="flex justify-center items-center w-4 h-4 bg-blue-500 rounded-full"
|
||||
>
|
||||
<div class="w-2 h-2 bg-white rounded-full"></div>
|
||||
<div class="w-2 h-2 bg-white rounded-full" />
|
||||
</div>
|
||||
<span class="text-sm font-medium text-n-slate-12"
|
||||
>Location Details</span
|
||||
>
|
||||
<span class="text-sm font-medium text-n-slate-12">{{
|
||||
t('WHATSAPP_TEMPLATES.PARSER.LOCATION_DETAILS')
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10">
|
||||
📍 Latitude <span class="text-red-500">*</span>
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.LATITUDE') }}
|
||||
<span class="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
v-model="processedParams.header.location.latitude"
|
||||
@@ -486,11 +447,14 @@ onMounted(() => {
|
||||
placeholder="37.7749 (San Francisco)"
|
||||
:message-type="getFieldErrorType('header.location.latitude')"
|
||||
/>
|
||||
<span class="text-xs text-n-slate-9">Range: -90.0 to 90.0</span>
|
||||
<span class="text-xs text-n-slate-9">{{
|
||||
t('WHATSAPP_TEMPLATES.PARSER.LATITUDE_RANGE')
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10">
|
||||
📍 Longitude <span class="text-red-500">*</span>
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.LONGITUDE') }}
|
||||
<span class="text-red-500">*</span>
|
||||
</label>
|
||||
<Input
|
||||
v-model="processedParams.header.location.longitude"
|
||||
@@ -501,13 +465,15 @@ onMounted(() => {
|
||||
placeholder="-122.4194 (San Francisco)"
|
||||
:message-type="getFieldErrorType('header.location.longitude')"
|
||||
/>
|
||||
<span class="text-xs text-n-slate-9">Range: -180.0 to 180.0</span>
|
||||
<span class="text-xs text-n-slate-9">{{
|
||||
t('WHATSAPP_TEMPLATES.PARSER.LONGITUDE_RANGE')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10">
|
||||
🏢 Location Name
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.LOCATION_NAME') }}
|
||||
</label>
|
||||
<Input
|
||||
v-model="processedParams.header.location.name"
|
||||
@@ -520,7 +486,7 @@ onMounted(() => {
|
||||
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10">
|
||||
📮 Full Address
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.FULL_ADDRESS') }}
|
||||
</label>
|
||||
<Input
|
||||
v-model="processedParams.header.location.address"
|
||||
@@ -534,17 +500,17 @@ onMounted(() => {
|
||||
<div
|
||||
class="p-2 text-xs bg-blue-50 rounded border-l-4 border-blue-400 text-n-slate-9"
|
||||
>
|
||||
💡 <strong>Tip:</strong> You can get coordinates by searching your
|
||||
location on Google Maps, right-clicking the pin, and copying the
|
||||
latitude/longitude values.
|
||||
<strong>{{ t('WHATSAPP_TEMPLATES.PARSER.TIP_LABEL') }}</strong>
|
||||
{{ t('WHATSAPP_TEMPLATES.PARSER.LOCATION_TIP') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Regular Header Parameters -->
|
||||
<div v-if="processedParams.header.location_type !== 'location'">
|
||||
<div
|
||||
v-for="(variable, key) in processedParams.header"
|
||||
v-if="!['location', 'location_type'].includes(key)"
|
||||
v-for="[key] in Object.entries(processedParams.header).filter(
|
||||
([k]) => !['location', 'location_type'].includes(k)
|
||||
)"
|
||||
:key="`header-${key}`"
|
||||
class="flex gap-2 items-center mb-2 w-full"
|
||||
>
|
||||
@@ -621,101 +587,6 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interactive Components -->
|
||||
<div v-if="processedParams.interactive" class="w-full">
|
||||
<h4 class="mb-2 text-sm font-medium text-n-slate-12">
|
||||
{{
|
||||
t('WHATSAPP_TEMPLATES.PARSER.INTERACTIVE_PARAMETERS') ||
|
||||
'Interactive Parameters'
|
||||
}}
|
||||
</h4>
|
||||
|
||||
<!-- Product Template -->
|
||||
<div
|
||||
v-if="processedParams.interactive.type === 'product'"
|
||||
class="mb-4 space-y-2"
|
||||
>
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10"
|
||||
>Catalog ID</label
|
||||
>
|
||||
<Input
|
||||
v-model="processedParams.interactive.catalog_id"
|
||||
custom-input-class="!h-8 w-full !bg-transparent"
|
||||
class="w-full"
|
||||
placeholder="Enter catalog ID"
|
||||
:message-type="getFieldErrorType('interactive.catalog_id')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10"
|
||||
>Product ID</label
|
||||
>
|
||||
<Input
|
||||
v-model="processedParams.interactive.product_id"
|
||||
custom-input-class="!h-8 w-full !bg-transparent"
|
||||
class="w-full"
|
||||
placeholder="Enter product retailer ID"
|
||||
:message-type="getFieldErrorType('interactive.product_id')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Catalog Template -->
|
||||
<div
|
||||
v-else-if="processedParams.interactive.type === 'catalog'"
|
||||
class="mb-4 space-y-2"
|
||||
>
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10"
|
||||
>Catalog ID</label
|
||||
>
|
||||
<Input
|
||||
v-model="processedParams.interactive.catalog_id"
|
||||
custom-input-class="!h-8 w-full !bg-transparent"
|
||||
class="w-full"
|
||||
placeholder="Enter catalog ID"
|
||||
:message-type="getFieldErrorType('interactive.catalog_id')"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10"
|
||||
>Browse Title</label
|
||||
>
|
||||
<Input
|
||||
v-model="processedParams.interactive.title"
|
||||
custom-input-class="!h-8 w-full !bg-transparent"
|
||||
class="w-full"
|
||||
placeholder="Browse Products"
|
||||
:message-type="getFieldErrorType('interactive.title')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- List Template -->
|
||||
<div
|
||||
v-else-if="processedParams.interactive.type === 'list'"
|
||||
class="mb-4 space-y-2"
|
||||
>
|
||||
<div>
|
||||
<label class="block mb-1 text-xs font-medium text-n-slate-10"
|
||||
>Button Text</label
|
||||
>
|
||||
<Input
|
||||
v-model="processedParams.interactive.button_text"
|
||||
custom-input-class="!h-8 w-full !bg-transparent"
|
||||
class="w-full"
|
||||
placeholder="Select an Option"
|
||||
:message-type="getFieldErrorType('interactive.button_text')"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-xs text-n-slate-10">
|
||||
List sections are configured in the template and cannot be modified
|
||||
here.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Button Variables -->
|
||||
<div v-if="processedParams.buttons" class="w-full">
|
||||
<h4 class="mb-2 text-sm font-medium text-n-slate-12">
|
||||
|
||||
+10
-3
@@ -35,8 +35,15 @@ export default {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since we support all formats now, include all approved templates
|
||||
// This includes templates with media headers (IMAGE, VIDEO, DOCUMENT)
|
||||
// Filter out interactive templates (LIST, PRODUCT, CATALOG)
|
||||
const hasInteractiveComponents = template.components.some(component =>
|
||||
['LIST', 'PRODUCT', 'CATALOG'].includes(component.type)
|
||||
);
|
||||
|
||||
if (hasInteractiveComponents) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
},
|
||||
@@ -106,7 +113,7 @@ export default {
|
||||
<span
|
||||
class="inline-block px-2 py-1 text-xs leading-none rounded-lg cursor-default bg-n-slate-3 text-n-slate-12"
|
||||
>
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.LABELS.LANGUAGE') }} :
|
||||
{{ $t('WHATSAPP_TEMPLATES.PICKER.LABELS.LANGUAGE') }}:
|
||||
{{ template.language }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,16 @@
|
||||
"VARIABLE_PLACEHOLDER": "Enter {variable} value",
|
||||
"GO_BACK_LABEL": "Go Back",
|
||||
"SEND_MESSAGE_LABEL": "Send Message",
|
||||
"FORM_ERROR_MESSAGE": "Please fill all variables before sending"
|
||||
"FORM_ERROR_MESSAGE": "Please fill all variables before sending",
|
||||
"LOCATION_DETAILS": "Location Details",
|
||||
"LATITUDE": "Latitude",
|
||||
"LONGITUDE": "Longitude",
|
||||
"LOCATION_NAME": "Location Name",
|
||||
"FULL_ADDRESS": "Full Address",
|
||||
"LATITUDE_RANGE": "Range: -90.0 to 90.0",
|
||||
"LONGITUDE_RANGE": "Range: -180.0 to 180.0",
|
||||
"LOCATION_TIP": "You can get coordinates by searching your location on Google Maps, right-clicking the pin, and copying the latitude/longitude values.",
|
||||
"TIP_LABEL": "Tip:"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,15 +12,6 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
end
|
||||
|
||||
def send_template(phone_number, template_info)
|
||||
# Check if this is an interactive template
|
||||
if template_info[:parameters].is_a?(Hash) && template_info[:parameters][:type] == 'interactive'
|
||||
send_interactive_template(phone_number, template_info)
|
||||
else
|
||||
send_regular_template(phone_number, template_info)
|
||||
end
|
||||
end
|
||||
|
||||
def send_regular_template(phone_number, template_info)
|
||||
template_body = template_body_parameters(template_info)
|
||||
|
||||
request_body = {
|
||||
@@ -40,32 +31,6 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def send_interactive_template(phone_number, template_info)
|
||||
interactive_data = template_info[:parameters][:interactive_data]
|
||||
|
||||
request_body = {
|
||||
messaging_product: 'whatsapp',
|
||||
recipient_type: 'individual',
|
||||
to: phone_number,
|
||||
type: 'interactive',
|
||||
interactive: {
|
||||
type: interactive_data[:type],
|
||||
body: {
|
||||
text: build_template_body_text(template_info)
|
||||
},
|
||||
action: interactive_data[:action]
|
||||
}
|
||||
}
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{phone_id_path}/messages",
|
||||
headers: api_headers,
|
||||
body: request_body.to_json
|
||||
)
|
||||
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
def sync_templates
|
||||
# ensuring that channels with wrong provider config wouldn't keep trying to sync templates
|
||||
whatsapp_channel.mark_message_templates_updated
|
||||
@@ -221,13 +186,4 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
||||
|
||||
process_response(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build_template_body_text(template_info)
|
||||
# Build the template body text with parameter substitution
|
||||
# This is a simplified version - you might want to enhance this
|
||||
# to properly substitute template variables
|
||||
template_info[:name] || 'Interactive Template'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -75,11 +75,8 @@ class Whatsapp::TemplateProcessorService
|
||||
template = find_template
|
||||
return if template.blank?
|
||||
|
||||
# Check for interactive templates first
|
||||
if has_interactive_components?(template)
|
||||
process_interactive_template_params(template)
|
||||
# Handle enhanced template parameters structure
|
||||
elsif template_params['processed_params'].is_a?(Hash) && template_params['processed_params'].key?('body')
|
||||
if template_params['processed_params'].is_a?(Hash) && template_params['processed_params'].key?('body')
|
||||
process_enhanced_template_params(template)
|
||||
else
|
||||
# Check if we have special header types that need processing
|
||||
@@ -190,7 +187,7 @@ class Whatsapp::TemplateProcessorService
|
||||
elsif key == 'location' && processed_params['header']['location_type'] == 'location'
|
||||
location_param = build_location_parameter(value)
|
||||
header_params << location_param if location_param
|
||||
elsif !%w[media_type location_type].include?(key)
|
||||
elsif %w[media_type location_type].exclude?(key)
|
||||
header_params << build_parameter(value)
|
||||
end
|
||||
end
|
||||
@@ -448,7 +445,7 @@ class Whatsapp::TemplateProcessorService
|
||||
when 'expiry'
|
||||
# Expiry time in minutes
|
||||
expiry_minutes = sanitized_value.to_i
|
||||
raise ArgumentError, 'Expiry minutes must be a positive number' unless expiry_minutes > 0
|
||||
raise ArgumentError, 'Expiry minutes must be a positive number' unless expiry_minutes.positive?
|
||||
|
||||
{ type: 'text', text: expiry_minutes.to_s }
|
||||
else
|
||||
@@ -494,145 +491,6 @@ class Whatsapp::TemplateProcessorService
|
||||
template['components'].find { |obj| obj['type'] == 'BODY' && obj.key?('text') }
|
||||
end
|
||||
|
||||
def has_interactive_components?(template)
|
||||
# Check if template has interactive buttons like quick replies, call-to-actions, etc.
|
||||
interactive_types = %w[quick_reply url phone_number copy_code list catalog_browse]
|
||||
template['components']&.any? do |component|
|
||||
(component['type'] == 'BUTTONS' && component['buttons']&.any? { |button| interactive_types.include?(button['type']) }) ||
|
||||
(component['type'] == 'LIST' && component['sections'].present?) ||
|
||||
(component['type'] == 'PRODUCT' && component['product_id'].present?) ||
|
||||
(component['type'] == 'CATALOG' && component['catalog_id'].present?)
|
||||
end
|
||||
end
|
||||
|
||||
def process_interactive_template_params(template)
|
||||
components = []
|
||||
|
||||
# Process body parameters
|
||||
if template_params['processed_params'].present?
|
||||
body_params = template_params['processed_params'].map { |_, value| { type: 'text', text: value } }
|
||||
components << { type: 'body', parameters: body_params } if body_params.present?
|
||||
end
|
||||
|
||||
@template_params = {
|
||||
type: 'interactive',
|
||||
components: components,
|
||||
interactive_data: extract_interactive_data(template)
|
||||
}
|
||||
end
|
||||
|
||||
def extract_interactive_data(template)
|
||||
# Check if this is a catalog browse template
|
||||
catalog_component = template['components'].find { |c| c['type'] == 'CATALOG' }
|
||||
return extract_catalog_data(catalog_component) if catalog_component&.dig('catalog_id')
|
||||
|
||||
# Check if this is a product template
|
||||
product_component = template['components'].find { |c| c['type'] == 'PRODUCT' }
|
||||
return extract_product_data(product_component) if product_component&.dig('product_id')
|
||||
|
||||
# Check if this is a list template
|
||||
list_component = template['components'].find { |c| c['type'] == 'LIST' }
|
||||
return extract_list_data(list_component) if list_component&.dig('sections')
|
||||
|
||||
# Default to button template
|
||||
interactive_data = { type: 'button', action: { buttons: [] } }
|
||||
|
||||
button_component = template['components'].find { |c| c['type'] == 'BUTTONS' }
|
||||
return interactive_data unless button_component&.dig('buttons')
|
||||
|
||||
buttons = button_component['buttons'].map.with_index do |button, index|
|
||||
# Process dynamic button text if parameters are provided
|
||||
button_text = process_dynamic_button_text(button['text'] || '', index)
|
||||
|
||||
case button['type']
|
||||
when 'quick_reply'
|
||||
{
|
||||
type: 'reply',
|
||||
reply: {
|
||||
id: "reply_#{index}",
|
||||
title: button_text || "Reply #{index + 1}"
|
||||
}
|
||||
}
|
||||
when 'url'
|
||||
{
|
||||
type: 'reply',
|
||||
reply: {
|
||||
id: "url_#{index}",
|
||||
title: button_text || 'Visit Link'
|
||||
}
|
||||
}
|
||||
when 'phone_number'
|
||||
{
|
||||
type: 'reply',
|
||||
reply: {
|
||||
id: "call_#{index}",
|
||||
title: button_text || 'Call Now'
|
||||
}
|
||||
}
|
||||
end
|
||||
end.compact.first(3) # WhatsApp allows max 3 reply buttons
|
||||
|
||||
interactive_data[:action][:buttons] = buttons
|
||||
interactive_data
|
||||
end
|
||||
|
||||
def extract_list_data(list_component)
|
||||
interactive_data = {
|
||||
type: 'list',
|
||||
action: {
|
||||
button: 'Select an Option',
|
||||
sections: []
|
||||
}
|
||||
}
|
||||
|
||||
sections = list_component['sections'].map.with_index do |section, section_index|
|
||||
rows = section['rows']&.map&.with_index do |row, row_index|
|
||||
{
|
||||
id: "row_#{section_index}_#{row_index}",
|
||||
title: row['title'] || "Option #{row_index + 1}",
|
||||
description: row['description'] || nil
|
||||
}
|
||||
end&.first(10) || [] # WhatsApp allows max 10 rows per section
|
||||
|
||||
{
|
||||
title: section['title'] || "Section #{section_index + 1}",
|
||||
rows: rows
|
||||
}
|
||||
end.first(10) # WhatsApp allows max 10 sections
|
||||
|
||||
interactive_data[:action][:sections] = sections
|
||||
interactive_data
|
||||
end
|
||||
|
||||
def extract_product_data(product_component)
|
||||
{
|
||||
type: 'product',
|
||||
action: {
|
||||
catalog_id: product_component['catalog_id'] || '',
|
||||
product_retailer_id: product_component['product_id']
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def extract_catalog_data(catalog_component)
|
||||
{
|
||||
type: 'product_list',
|
||||
action: {
|
||||
catalog_id: catalog_component['catalog_id'],
|
||||
sections: [
|
||||
{
|
||||
title: catalog_component['title'] || 'Browse Products',
|
||||
product_items: catalog_component['products']&.map do |product|
|
||||
{
|
||||
product_retailer_id: product['id'] || product['product_id']
|
||||
}
|
||||
end || []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def process_dynamic_button_text(button_text, button_index)
|
||||
return button_text unless button_text.include?('{{')
|
||||
|
||||
@@ -651,7 +509,7 @@ class Whatsapp::TemplateProcessorService
|
||||
button_text
|
||||
end
|
||||
|
||||
def has_rich_formatting?(text)
|
||||
def rich_formatting?(text)
|
||||
# Check if text contains WhatsApp rich formatting markers
|
||||
text.match?(/\*[^*]+\*/) || # Bold: *text*
|
||||
text.match?(/_[^_]+_/) || # Italic: _text_
|
||||
|
||||
Reference in New Issue
Block a user