Add static and dynamic url
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
dvirlabs 2026-05-12 03:34:07 +03:00
parent cbcac9d1fd
commit b238e7fc47
4 changed files with 72 additions and 37 deletions

View File

@ -695,7 +695,8 @@ async def create_whatsapp_template(
"body_param_keys": ["groom_name", "bride_name", ...], "body_param_keys": ["groom_name", "bride_name", ...],
"button_type": "URL", # optional button type "button_type": "URL", # optional button type
"button_text": "Visit Website", # optional button label "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": "חבר", ... } "fallbacks": { "contact_name": "חבר", ... }
} }
""" """
@ -724,6 +725,7 @@ async def create_whatsapp_template(
"button_type": body.get("button_type", ""), "button_type": body.get("button_type", ""),
"button_text": body.get("button_text", ""), "button_text": body.get("button_text", ""),
"button_url": body.get("button_url", ""), "button_url": body.get("button_url", ""),
"button_param_key": body.get("button_param_key", ""),
"fallbacks": body.get("fallbacks", {}), "fallbacks": body.get("fallbacks", {}),
"guest_name_key": body.get("guest_name_key", ""), "guest_name_key": body.get("guest_name_key", ""),
} }

View File

@ -492,13 +492,23 @@ class WhatsAppService:
"parameters": [{"type": "text", "text": str(v)} for v in body_values], "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: if button_type == "URL" and button_url:
# For URL buttons with dynamic suffixes, extract the dynamic part from params button_param_key = tpl.get("button_param_key", "")
# URL buttons in Meta templates can have a static prefix and dynamic suffix # Check if URL has {{1}} placeholder for dynamic parameter
# Example: https://example.com/{1} where {1} is replaced by a param if "{{1}}" in button_url and button_param_key:
# If no dynamic param, the button is purely static (no parameters needed) # Dynamic URL button - need to send the parameter value
pass # Static URL buttons don't need parameters in the API call 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) # Handle url_button component if defined in template (legacy dynamic buttons)
url_btn = tpl.get("url_button", {}) url_btn = tpl.get("url_button", {})

View File

@ -229,6 +229,7 @@ def list_templates_for_frontend() -> list:
"button_type": tpl.get("button_type", ""), "button_type": tpl.get("button_type", ""),
"button_text": tpl.get("button_text", ""), "button_text": tpl.get("button_text", ""),
"button_url": tpl.get("button_url", ""), "button_url": tpl.get("button_url", ""),
"button_param_key": tpl.get("button_param_key", ""),
"guest_name_key": tpl.get("guest_name_key", ""), "guest_name_key": tpl.get("guest_name_key", ""),
"url_button": tpl.get("url_button", None), "url_button": tpl.get("url_button", None),
} }

View File

@ -37,6 +37,7 @@ const he = {
buttonType: 'סוג כפתור', buttonType: 'סוג כפתור',
buttonText: 'טקסט הכפתור', buttonText: 'טקסט הכפתור',
buttonUrl: 'כתובת URL', buttonUrl: 'כתובת URL',
buttonParamKey: 'פרמטר דינמי עבור {{1}}',
uploadImage: 'העלה תמונה', uploadImage: 'העלה תמונה',
uploading: 'מעלה...', uploading: 'מעלה...',
paramMapping: 'מיפוי פרמטרים', paramMapping: 'מיפוי פרמטרים',
@ -51,7 +52,8 @@ const he = {
headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת', headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת',
headerHandleHint: 'העלה תמונה או הדבק קישור לתמונה מ-Meta Media Library', headerHandleHint: 'העלה תמונה או הדבק קישור לתמונה מ-Meta Media Library',
bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta', bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta',
buttonUrlHint: 'כתובת URL מלאה, לדוגמה: https://invy.dvirlabs.com', buttonUrlHint: 'לכתובת דינמית השתמש ב-{{1}} בסוף הכתובת, לדוגמה: https://invy.dvirlabs.com/guest/{{1}}',
buttonParamHint: 'בחר איזה פרמטר ימולא במקום {{1}} בכתובת הכפתור',
keyHint: 'אותיות קטנות, מספרים ו-_ בלבד', keyHint: 'אותיות קטנות, מספרים ו-_ בלבד',
metaHint: 'שם מדויק כפי שאושר ב-Meta Business Manager', metaHint: 'שם מדויק כפי שאושר ב-Meta Business Manager',
saved: '✓ התבנית נשמרה בהצלחה!', saved: '✓ התבנית נשמרה בהצלחה!',
@ -85,7 +87,7 @@ const EMPTY_FORM = {
language: 'he', description: '', language: 'he', description: '',
headerType: 'TEXT', headerText: '', headerHandle: '', headerType: 'TEXT', headerText: '', headerHandle: '',
bodyText: '', bodyText: '',
buttonType: '', buttonText: '', buttonUrl: '', buttonType: '', buttonText: '', buttonUrl: '', buttonParamKey: '',
} }
export default function TemplateEditor({ onBack }) { export default function TemplateEditor({ onBack }) {
@ -213,6 +215,7 @@ export default function TemplateEditor({ onBack }) {
buttonType: tpl.button_type || '', buttonType: tpl.button_type || '',
buttonText: tpl.button_text || '', buttonText: tpl.button_text || '',
buttonUrl: tpl.button_url || '', buttonUrl: tpl.button_url || '',
buttonParamKey: tpl.button_param_key || '',
}) })
setEditMode(true) setEditMode(true)
setEditingKey(tpl.key) setEditingKey(tpl.key)
@ -249,6 +252,7 @@ export default function TemplateEditor({ onBack }) {
button_type: form.buttonType, button_type: form.buttonType,
button_text: form.buttonText.trim(), button_text: form.buttonText.trim(),
button_url: form.buttonUrl.trim(), button_url: form.buttonUrl.trim(),
button_param_key: form.buttonParamKey,
fallbacks: Object.fromEntries(PARAM_OPTIONS.map(o => [o.key, o.sample])), fallbacks: Object.fromEntries(PARAM_OPTIONS.map(o => [o.key, o.sample])),
guest_name_key: guestNameKey, guest_name_key: guestNameKey,
}) })
@ -453,10 +457,28 @@ export default function TemplateEditor({ onBack }) {
<div className="te-field"> <div className="te-field">
<label>{he.buttonUrl}</label> <label>{he.buttonUrl}</label>
<input name="buttonUrl" value={form.buttonUrl} <input name="buttonUrl" value={form.buttonUrl}
onChange={handleInput} placeholder="https://invy.dvirlabs.com" onChange={handleInput} placeholder="https://invy.dvirlabs.com/guest/{{1}}"
disabled={saving} dir="ltr" /> disabled={saving} dir="ltr" />
<small className="te-hint">{he.buttonUrlHint}</small> <small className="te-hint">{he.buttonUrlHint}</small>
</div> </div>
{form.buttonUrl.includes('{{1}}') && (
<div className="te-field">
<label>{he.buttonParamKey}</label>
<select
name="buttonParamKey"
value={form.buttonParamKey}
onChange={handleInput}
disabled={saving}
dir="ltr"
>
<option value=""> בחר פרמטר </option>
{PARAM_OPTIONS.map(o => (
<option key={o.key} value={o.key}>{o.label} ({o.key})</option>
))}
</select>
<small className="te-hint">{he.buttonParamHint}</small>
</div>
)}
</> </>
)} )}
</div> </div>