diff --git a/backend/main.py b/backend/main.py index 9164ea8..e5b9221 100644 --- a/backend/main.py +++ b/backend/main.py @@ -695,7 +695,8 @@ async def create_whatsapp_template( "body_param_keys": ["groom_name", "bride_name", ...], "button_type": "URL", # optional button type "button_text": "Visit Website", # optional button label - "button_url": "https://...", # optional button URL + "button_url": "https://...{{1}}", # optional button URL (use {{1}} for dynamic) + "button_param_key": "event_id", # param key for button {{1}} placeholder "fallbacks": { "contact_name": "חבר", ... } } """ @@ -711,21 +712,22 @@ async def create_whatsapp_template( raise HTTPException(status_code=400, detail="'friendly_name' is required") template = { - "meta_name": body.get("meta_name", key), - "language_code": body.get("language_code", "he"), - "friendly_name": body["friendly_name"], - "description": body.get("description", ""), - "header_type": body.get("header_type", "TEXT"), - "header_text": body.get("header_text", ""), - "header_handle": body.get("header_handle", ""), - "body_text": body.get("body_text", ""), - "header_params": body.get("header_param_keys", []), - "body_params": body.get("body_param_keys", []), - "button_type": body.get("button_type", ""), - "button_text": body.get("button_text", ""), - "button_url": body.get("button_url", ""), - "fallbacks": body.get("fallbacks", {}), - "guest_name_key": body.get("guest_name_key", ""), + "meta_name": body.get("meta_name", key), + "language_code": body.get("language_code", "he"), + "friendly_name": body["friendly_name"], + "description": body.get("description", ""), + "header_type": body.get("header_type", "TEXT"), + "header_text": body.get("header_text", ""), + "header_handle": body.get("header_handle", ""), + "body_text": body.get("body_text", ""), + "header_params": body.get("header_param_keys", []), + "body_params": body.get("body_param_keys", []), + "button_type": body.get("button_type", ""), + "button_text": body.get("button_text", ""), + "button_url": body.get("button_url", ""), + "button_param_key": body.get("button_param_key", ""), + "fallbacks": body.get("fallbacks", {}), + "guest_name_key": body.get("guest_name_key", ""), } try: diff --git a/backend/whatsapp.py b/backend/whatsapp.py index 81445f1..2c34756 100644 --- a/backend/whatsapp.py +++ b/backend/whatsapp.py @@ -492,13 +492,23 @@ class WhatsAppService: "parameters": [{"type": "text", "text": str(v)} for v in body_values], }) - # Handle static URL button + # Handle URL button with dynamic parameters + # Meta WhatsApp supports dynamic URL suffixes like: https://example.com/guest/{{1}} + # where {{1}} is replaced by a dynamic parameter if button_type == "URL" and button_url: - # For URL buttons with dynamic suffixes, extract the dynamic part from params - # URL buttons in Meta templates can have a static prefix and dynamic suffix - # Example: https://example.com/{1} where {1} is replaced by a param - # If no dynamic param, the button is purely static (no parameters needed) - pass # Static URL buttons don't need parameters in the API call + button_param_key = tpl.get("button_param_key", "") + # Check if URL has {{1}} placeholder for dynamic parameter + if "{{1}}" in button_url and button_param_key: + # Dynamic URL button - need to send the parameter value + param_value = str(params.get(button_param_key, "")).strip() + if param_value: + components.append({ + "type": "button", + "sub_type": "url", + "index": "0", + "parameters": [{"type": "text", "text": param_value}], + }) + # else: Static URL button - no parameters needed in the API call # Handle url_button component if defined in template (legacy dynamic buttons) url_btn = tpl.get("url_button", {}) diff --git a/backend/whatsapp_templates.py b/backend/whatsapp_templates.py index dca4f96..4c77427 100644 --- a/backend/whatsapp_templates.py +++ b/backend/whatsapp_templates.py @@ -229,6 +229,7 @@ def list_templates_for_frontend() -> list: "button_type": tpl.get("button_type", ""), "button_text": tpl.get("button_text", ""), "button_url": tpl.get("button_url", ""), + "button_param_key": tpl.get("button_param_key", ""), "guest_name_key": tpl.get("guest_name_key", ""), "url_button": tpl.get("url_button", None), } diff --git a/frontend/src/components/TemplateEditor.jsx b/frontend/src/components/TemplateEditor.jsx index 6894e78..32bb65b 100644 --- a/frontend/src/components/TemplateEditor.jsx +++ b/frontend/src/components/TemplateEditor.jsx @@ -37,6 +37,7 @@ const he = { buttonType: 'סוג כפתור', buttonText: 'טקסט הכפתור', buttonUrl: 'כתובת URL', + buttonParamKey: 'פרמטר דינמי עבור {{1}}', uploadImage: 'העלה תמונה', uploading: 'מעלה...', paramMapping: 'מיפוי פרמטרים', @@ -51,7 +52,8 @@ const he = { headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת', headerHandleHint: 'העלה תמונה או הדבק קישור לתמונה מ-Meta Media Library', bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta', - buttonUrlHint: 'כתובת URL מלאה, לדוגמה: https://invy.dvirlabs.com', + buttonUrlHint: 'לכתובת דינמית השתמש ב-{{1}} בסוף הכתובת, לדוגמה: https://invy.dvirlabs.com/guest/{{1}}', + buttonParamHint: 'בחר איזה פרמטר ימולא במקום {{1}} בכתובת הכפתור', keyHint: 'אותיות קטנות, מספרים ו-_ בלבד', metaHint: 'שם מדויק כפי שאושר ב-Meta Business Manager', saved: '✓ התבנית נשמרה בהצלחה!', @@ -85,7 +87,7 @@ const EMPTY_FORM = { language: 'he', description: '', headerType: 'TEXT', headerText: '', headerHandle: '', bodyText: '', - buttonType: '', buttonText: '', buttonUrl: '', + buttonType: '', buttonText: '', buttonUrl: '', buttonParamKey: '', } export default function TemplateEditor({ onBack }) { @@ -201,18 +203,19 @@ export default function TemplateEditor({ onBack }) { setBPK(tpl.body_params || []) setGuestNameKey(tpl.guest_name_key || '') setForm({ - key: tpl.key, - friendlyName: tpl.friendly_name, - metaName: tpl.meta_name, - language: tpl.language_code || 'he', - description: tpl.description || '', - headerType: tpl.header_type || 'TEXT', - headerText: tpl.header_text || '', - headerHandle: tpl.header_handle || '', - bodyText: tpl.body_text || '', - buttonType: tpl.button_type || '', - buttonText: tpl.button_text || '', - buttonUrl: tpl.button_url || '', + key: tpl.key, + friendlyName: tpl.friendly_name, + metaName: tpl.meta_name, + language: tpl.language_code || 'he', + description: tpl.description || '', + headerType: tpl.header_type || 'TEXT', + headerText: tpl.header_text || '', + headerHandle: tpl.header_handle || '', + bodyText: tpl.body_text || '', + buttonType: tpl.button_type || '', + buttonText: tpl.button_text || '', + buttonUrl: tpl.button_url || '', + buttonParamKey: tpl.button_param_key || '', }) setEditMode(true) setEditingKey(tpl.key) @@ -249,6 +252,7 @@ export default function TemplateEditor({ onBack }) { button_type: form.buttonType, button_text: form.buttonText.trim(), button_url: form.buttonUrl.trim(), + button_param_key: form.buttonParamKey, fallbacks: Object.fromEntries(PARAM_OPTIONS.map(o => [o.key, o.sample])), guest_name_key: guestNameKey, }) @@ -453,10 +457,28 @@ export default function TemplateEditor({ onBack }) {
{he.buttonUrlHint}
+ {form.buttonUrl.includes('{{1}}') && ( +
+ + + {he.buttonParamHint} +
+ )} )}