Update template editor
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
ea36c5ede9
commit
2644ebd0d3
@ -687,10 +687,15 @@ async def create_whatsapp_template(
|
|||||||
"meta_name": "my_template", # exact name in Meta BM
|
"meta_name": "my_template", # exact name in Meta BM
|
||||||
"language_code": "he",
|
"language_code": "he",
|
||||||
"description": "optional description",
|
"description": "optional description",
|
||||||
"header_text": "היי {{1}}", # raw text (for preview)
|
"header_type": "TEXT" or "IMAGE", # header type
|
||||||
|
"header_text": "היי {{1}}", # raw text (for preview, TEXT headers)
|
||||||
|
"header_handle": "https://...", # media URL or handle (IMAGE headers)
|
||||||
"body_text": "{{1}} ו-{{2}} ...", # raw text (for preview)
|
"body_text": "{{1}} ו-{{2}} ...", # raw text (for preview)
|
||||||
"header_param_keys": ["contact_name"], # ordered param keys for header {{N}}
|
"header_param_keys": ["contact_name"], # ordered param keys for header {{N}}
|
||||||
"body_param_keys": ["groom_name", "bride_name", ...],
|
"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
|
||||||
"fallbacks": { "contact_name": "חבר", ... }
|
"fallbacks": { "contact_name": "חבר", ... }
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@ -710,10 +715,15 @@ async def create_whatsapp_template(
|
|||||||
"language_code": body.get("language_code", "he"),
|
"language_code": body.get("language_code", "he"),
|
||||||
"friendly_name": body["friendly_name"],
|
"friendly_name": body["friendly_name"],
|
||||||
"description": body.get("description", ""),
|
"description": body.get("description", ""),
|
||||||
|
"header_type": body.get("header_type", "TEXT"),
|
||||||
"header_text": body.get("header_text", ""),
|
"header_text": body.get("header_text", ""),
|
||||||
|
"header_handle": body.get("header_handle", ""),
|
||||||
"body_text": body.get("body_text", ""),
|
"body_text": body.get("body_text", ""),
|
||||||
"header_params": body.get("header_param_keys", []),
|
"header_params": body.get("header_param_keys", []),
|
||||||
"body_params": body.get("body_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", {}),
|
"fallbacks": body.get("fallbacks", {}),
|
||||||
"guest_name_key": body.get("guest_name_key", ""),
|
"guest_name_key": body.get("guest_name_key", ""),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -441,6 +441,10 @@ class WhatsAppService:
|
|||||||
tpl = get_template(template_key)
|
tpl = get_template(template_key)
|
||||||
meta_name = tpl["meta_name"]
|
meta_name = tpl["meta_name"]
|
||||||
language_code = tpl.get("language_code", "he")
|
language_code = tpl.get("language_code", "he")
|
||||||
|
header_type = tpl.get("header_type", "TEXT")
|
||||||
|
header_handle = tpl.get("header_handle", "")
|
||||||
|
button_type = tpl.get("button_type", "")
|
||||||
|
button_url = tpl.get("button_url", "")
|
||||||
|
|
||||||
header_values, body_values = build_params_list(template_key, params)
|
header_values, body_values = build_params_list(template_key, params)
|
||||||
|
|
||||||
@ -449,18 +453,54 @@ class WhatsAppService:
|
|||||||
raise WhatsAppError(f"Invalid phone number: {to_phone}")
|
raise WhatsAppError(f"Invalid phone number: {to_phone}")
|
||||||
|
|
||||||
components = []
|
components = []
|
||||||
if header_values:
|
|
||||||
|
# Build header component based on type
|
||||||
|
if header_type == "IMAGE" and header_handle:
|
||||||
|
components.append({
|
||||||
|
"type": "header",
|
||||||
|
"parameters": [{
|
||||||
|
"type": "image",
|
||||||
|
"image": {"link": header_handle}
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
elif header_type == "VIDEO" and header_handle:
|
||||||
|
components.append({
|
||||||
|
"type": "header",
|
||||||
|
"parameters": [{
|
||||||
|
"type": "video",
|
||||||
|
"video": {"link": header_handle}
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
elif header_type == "DOCUMENT" and header_handle:
|
||||||
|
components.append({
|
||||||
|
"type": "header",
|
||||||
|
"parameters": [{
|
||||||
|
"type": "document",
|
||||||
|
"document": {"link": header_handle}
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
elif header_type == "TEXT" and header_values:
|
||||||
components.append({
|
components.append({
|
||||||
"type": "header",
|
"type": "header",
|
||||||
"parameters": [{"type": "text", "text": str(v)} for v in header_values],
|
"parameters": [{"type": "text", "text": str(v)} for v in header_values],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Build body component
|
||||||
if body_values:
|
if body_values:
|
||||||
components.append({
|
components.append({
|
||||||
"type": "body",
|
"type": "body",
|
||||||
"parameters": [{"type": "text", "text": str(v)} for v in body_values],
|
"parameters": [{"type": "text", "text": str(v)} for v in body_values],
|
||||||
})
|
})
|
||||||
|
|
||||||
# Handle url_button component if defined in template
|
# Handle static URL button
|
||||||
|
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
|
||||||
|
|
||||||
|
# Handle url_button component if defined in template (legacy dynamic buttons)
|
||||||
url_btn = tpl.get("url_button", {})
|
url_btn = tpl.get("url_button", {})
|
||||||
if url_btn and url_btn.get("enabled"):
|
if url_btn and url_btn.get("enabled"):
|
||||||
param_key = url_btn.get("param_key", "event_id")
|
param_key = url_btn.get("param_key", "event_id")
|
||||||
@ -487,7 +527,7 @@ class WhatsAppService:
|
|||||||
import json
|
import json
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[WhatsApp] send_by_template_key '{template_key}' → meta='{meta_name}' "
|
f"[WhatsApp] send_by_template_key '{template_key}' → meta='{meta_name}' "
|
||||||
f"lang={language_code} to={to_e164} "
|
f"lang={language_code} to={to_e164} header_type={header_type} "
|
||||||
f"header_params={header_values} body_params={body_values}"
|
f"header_params={header_values} body_params={body_values}"
|
||||||
)
|
)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
@ -10,9 +10,14 @@ How to add a new template:
|
|||||||
- language_code : he / he_IL / en / en_US …
|
- language_code : he / he_IL / en / en_US …
|
||||||
- friendly_name : shown in the frontend dropdown
|
- friendly_name : shown in the frontend dropdown
|
||||||
- description : optional, for documentation
|
- description : optional, for documentation
|
||||||
|
- header_type : "TEXT" or "IMAGE" or "VIDEO" or "DOCUMENT" (default: "TEXT")
|
||||||
- header_params : ordered list of variable keys sent in the HEADER component
|
- header_params : ordered list of variable keys sent in the HEADER component
|
||||||
(empty list [] if the template has no header variables)
|
(empty list [] if the template has no header variables)
|
||||||
|
- header_handle : media handle for IMAGE/VIDEO/DOCUMENT headers (optional)
|
||||||
- body_params : ordered list of variable keys sent in the BODY component
|
- body_params : ordered list of variable keys sent in the BODY component
|
||||||
|
- button_type : "URL" or "PHONE_NUMBER" or "QUICK_REPLY" (optional)
|
||||||
|
- button_text : button label/text (optional)
|
||||||
|
- button_url : button URL (optional, for URL buttons)
|
||||||
- fallbacks : dict {key: default_string} used when the caller doesn't
|
- fallbacks : dict {key: default_string} used when the caller doesn't
|
||||||
provide a value for that key
|
provide a value for that key
|
||||||
|
|
||||||
@ -219,6 +224,11 @@ def list_templates_for_frontend() -> list:
|
|||||||
"header_params": tpl["header_params"],
|
"header_params": tpl["header_params"],
|
||||||
"body_text": tpl.get("body_text", ""),
|
"body_text": tpl.get("body_text", ""),
|
||||||
"header_text": tpl.get("header_text", ""),
|
"header_text": tpl.get("header_text", ""),
|
||||||
|
"header_type": tpl.get("header_type", "TEXT"),
|
||||||
|
"header_handle": tpl.get("header_handle", ""),
|
||||||
|
"button_type": tpl.get("button_type", ""),
|
||||||
|
"button_text": tpl.get("button_text", ""),
|
||||||
|
"button_url": tpl.get("button_url", ""),
|
||||||
"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),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -400,6 +400,62 @@
|
|||||||
border-bottom-color: rgba(255,255,255,0.08);
|
border-bottom-color: rgba(255,255,255,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.te-bubble-header-media {
|
||||||
|
margin: -0.65rem -0.85rem 0.5rem -0.85rem;
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.te-bubble-header-media img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
object-fit: cover;
|
||||||
|
max-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.te-media-placeholder {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%);
|
||||||
|
color: #999;
|
||||||
|
font-size: 2rem;
|
||||||
|
min-height: 120px;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .te-media-placeholder {
|
||||||
|
background: linear-gradient(135deg, #2a2d3d 0%, #1f2230 100%);
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.te-bubble-button {
|
||||||
|
margin: 0.6rem -0.35rem 0;
|
||||||
|
padding: 0.6rem;
|
||||||
|
text-align: center;
|
||||||
|
color: #0a7cff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border-top: 1px solid rgba(0,0,0,0.08);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.te-bubble-button:hover {
|
||||||
|
background: rgba(0,0,0,0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .te-bubble-button {
|
||||||
|
border-top-color: rgba(255,255,255,0.08);
|
||||||
|
color: #4a9eff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .te-bubble-button:hover {
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
.te-bubble-body {
|
.te-bubble-body {
|
||||||
color: #333;
|
color: #333;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|||||||
@ -29,8 +29,14 @@ const he = {
|
|||||||
description: 'תיאור',
|
description: 'תיאור',
|
||||||
headerSection: 'כותרת (Header) — אופציונלי',
|
headerSection: 'כותרת (Header) — אופציונלי',
|
||||||
bodySection: 'גוף ההודעה (Body)',
|
bodySection: 'גוף ההודעה (Body)',
|
||||||
|
buttonSection: 'כפתור (Button) — אופציונלי',
|
||||||
|
headerType: 'סוג כותרת',
|
||||||
headerText: 'טקסט הכותרת',
|
headerText: 'טקסט הכותרת',
|
||||||
|
headerHandle: 'קישור/מזהה תמונה',
|
||||||
bodyText: 'טקסט ההודעה',
|
bodyText: 'טקסט ההודעה',
|
||||||
|
buttonType: 'סוג כפתור',
|
||||||
|
buttonText: 'טקסט הכפתור',
|
||||||
|
buttonUrl: 'כתובת URL',
|
||||||
paramMapping: 'מיפוי פרמטרים',
|
paramMapping: 'מיפוי פרמטרים',
|
||||||
preview: 'תצוגה מקדימה',
|
preview: 'תצוגה מקדימה',
|
||||||
save: 'שמור תבנית',
|
save: 'שמור תבנית',
|
||||||
@ -41,7 +47,9 @@ const he = {
|
|||||||
builtIn: 'מובנת',
|
builtIn: 'מובנת',
|
||||||
chars: 'תווים',
|
chars: 'תווים',
|
||||||
headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת',
|
headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת',
|
||||||
|
headerHandleHint: 'קישור לתמונה או מזהה Media שהועלה ל-Meta',
|
||||||
bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta',
|
bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta',
|
||||||
|
buttonUrlHint: 'כתובת URL מלאה, לדוגמה: https://invy.dvirlabs.com',
|
||||||
keyHint: 'אותיות קטנות, מספרים ו-_ בלבד',
|
keyHint: 'אותיות קטנות, מספרים ו-_ בלבד',
|
||||||
metaHint: 'שם מדויק כפי שאושר ב-Meta Business Manager',
|
metaHint: 'שם מדויק כפי שאושר ב-Meta Business Manager',
|
||||||
saved: '✓ התבנית נשמרה בהצלחה!',
|
saved: '✓ התבנית נשמרה בהצלחה!',
|
||||||
@ -73,7 +81,9 @@ function renderPreview(text, paramKeys) {
|
|||||||
const EMPTY_FORM = {
|
const EMPTY_FORM = {
|
||||||
key: '', friendlyName: '', metaName: '',
|
key: '', friendlyName: '', metaName: '',
|
||||||
language: 'he', description: '',
|
language: 'he', description: '',
|
||||||
headerText: '', bodyText: '',
|
headerType: 'TEXT', headerText: '', headerHandle: '',
|
||||||
|
bodyText: '',
|
||||||
|
buttonType: '', buttonText: '', buttonUrl: '',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TemplateEditor({ onBack }) {
|
export default function TemplateEditor({ onBack }) {
|
||||||
@ -160,8 +170,13 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
metaName: tpl.meta_name,
|
metaName: tpl.meta_name,
|
||||||
language: tpl.language_code || 'he',
|
language: tpl.language_code || 'he',
|
||||||
description: tpl.description || '',
|
description: tpl.description || '',
|
||||||
|
headerType: tpl.header_type || 'TEXT',
|
||||||
headerText: tpl.header_text || '',
|
headerText: tpl.header_text || '',
|
||||||
|
headerHandle: tpl.header_handle || '',
|
||||||
bodyText: tpl.body_text || '',
|
bodyText: tpl.body_text || '',
|
||||||
|
buttonType: tpl.button_type || '',
|
||||||
|
buttonText: tpl.button_text || '',
|
||||||
|
buttonUrl: tpl.button_url || '',
|
||||||
})
|
})
|
||||||
setEditMode(true)
|
setEditMode(true)
|
||||||
setEditingKey(tpl.key)
|
setEditingKey(tpl.key)
|
||||||
@ -189,10 +204,15 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
meta_name: form.metaName.trim(),
|
meta_name: form.metaName.trim(),
|
||||||
language_code: form.language,
|
language_code: form.language,
|
||||||
description: form.description.trim(),
|
description: form.description.trim(),
|
||||||
|
header_type: form.headerType,
|
||||||
header_text: form.headerText.trim(),
|
header_text: form.headerText.trim(),
|
||||||
|
header_handle: form.headerHandle.trim(),
|
||||||
body_text: form.bodyText.trim(),
|
body_text: form.bodyText.trim(),
|
||||||
header_param_keys: headerParamKeys,
|
header_param_keys: headerParamKeys,
|
||||||
body_param_keys: bodyParamKeys,
|
body_param_keys: bodyParamKeys,
|
||||||
|
button_type: form.buttonType,
|
||||||
|
button_text: form.buttonText.trim(),
|
||||||
|
button_url: form.buttonUrl.trim(),
|
||||||
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,
|
||||||
})
|
})
|
||||||
@ -292,6 +312,17 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
|
|
||||||
<div className="te-card">
|
<div className="te-card">
|
||||||
<h3 className="te-card-title">{he.headerSection}</h3>
|
<h3 className="te-card-title">{he.headerSection}</h3>
|
||||||
|
<div className="te-field">
|
||||||
|
<label>{he.headerType}</label>
|
||||||
|
<select name="headerType" value={form.headerType}
|
||||||
|
onChange={handleInput} disabled={saving}>
|
||||||
|
<option value="TEXT">טקסט (TEXT)</option>
|
||||||
|
<option value="IMAGE">תמונה (IMAGE)</option>
|
||||||
|
<option value="VIDEO">וידאו (VIDEO)</option>
|
||||||
|
<option value="DOCUMENT">מסמך (DOCUMENT)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{form.headerType === 'TEXT' ? (
|
||||||
<div className="te-field">
|
<div className="te-field">
|
||||||
<div className="te-label-row">
|
<div className="te-label-row">
|
||||||
<label>{he.headerText}</label>
|
<label>{he.headerText}</label>
|
||||||
@ -302,6 +333,15 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
disabled={saving} maxLength={60} dir="rtl" />
|
disabled={saving} maxLength={60} dir="rtl" />
|
||||||
<small className="te-hint">{he.headerHint}</small>
|
<small className="te-hint">{he.headerHint}</small>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="te-field">
|
||||||
|
<label>{he.headerHandle}</label>
|
||||||
|
<input name="headerHandle" value={form.headerHandle}
|
||||||
|
onChange={handleInput} placeholder="https://example.com/image.jpg"
|
||||||
|
disabled={saving} dir="ltr" />
|
||||||
|
<small className="te-hint">{he.headerHandleHint}</small>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="te-card">
|
<div className="te-card">
|
||||||
@ -320,6 +360,37 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="te-card">
|
||||||
|
<h3 className="te-card-title">{he.buttonSection}</h3>
|
||||||
|
<div className="te-field">
|
||||||
|
<label>{he.buttonType}</label>
|
||||||
|
<select name="buttonType" value={form.buttonType}
|
||||||
|
onChange={handleInput} disabled={saving}>
|
||||||
|
<option value="">ללא כפתור</option>
|
||||||
|
<option value="URL">URL — קישור לאתר</option>
|
||||||
|
<option value="PHONE_NUMBER">מספר טלפון</option>
|
||||||
|
<option value="QUICK_REPLY">תגובה מהירה</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{form.buttonType === 'URL' && (
|
||||||
|
<>
|
||||||
|
<div className="te-field">
|
||||||
|
<label>{he.buttonText}</label>
|
||||||
|
<input name="buttonText" value={form.buttonText}
|
||||||
|
onChange={handleInput} placeholder="Visit website"
|
||||||
|
disabled={saving} maxLength={25} />
|
||||||
|
</div>
|
||||||
|
<div className="te-field">
|
||||||
|
<label>{he.buttonUrl}</label>
|
||||||
|
<input name="buttonUrl" value={form.buttonUrl}
|
||||||
|
onChange={handleInput} placeholder="https://invy.dvirlabs.com"
|
||||||
|
disabled={saving} dir="ltr" />
|
||||||
|
<small className="te-hint">{he.buttonUrlHint}</small>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{(hNums.length > 0 || bNums.length > 0) && (
|
{(hNums.length > 0 || bNums.length > 0) && (
|
||||||
<div className="te-card te-params-card">
|
<div className="te-card te-params-card">
|
||||||
<h3 className="te-card-title">{he.paramMapping}</h3>
|
<h3 className="te-card-title">{he.paramMapping}</h3>
|
||||||
@ -417,12 +488,30 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
<h3 className="te-card-title">{he.preview}</h3>
|
<h3 className="te-card-title">{he.preview}</h3>
|
||||||
<div className="te-phone-mockup">
|
<div className="te-phone-mockup">
|
||||||
<div className="te-bubble">
|
<div className="te-bubble">
|
||||||
{previewHeader && <div className="te-bubble-header">{previewHeader}</div>}
|
{form.headerType === 'IMAGE' && form.headerHandle && (
|
||||||
|
<div className="te-bubble-header-media">
|
||||||
|
<img src={form.headerHandle} alt="Header" onError={(e) => {
|
||||||
|
e.target.style.display = 'none'
|
||||||
|
e.target.nextElementSibling.style.display = 'flex'
|
||||||
|
}} />
|
||||||
|
<div className="te-media-placeholder" style={{display: 'none'}}>
|
||||||
|
🖼️ תמונת כותרת
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{form.headerType === 'TEXT' && previewHeader && (
|
||||||
|
<div className="te-bubble-header">{previewHeader}</div>
|
||||||
|
)}
|
||||||
<div className="te-bubble-body">
|
<div className="te-bubble-body">
|
||||||
{previewBody
|
{previewBody
|
||||||
? previewBody.split('\n').map((ln, i) => <span key={i}>{ln}<br /></span>)
|
? previewBody.split('\n').map((ln, i) => <span key={i}>{ln}<br /></span>)
|
||||||
: <span className="te-placeholder">ההודעה תופיע כאן...</span>}
|
: <span className="te-placeholder">ההודעה תופיע כאן...</span>}
|
||||||
</div>
|
</div>
|
||||||
|
{form.buttonType === 'URL' && form.buttonText && (
|
||||||
|
<div className="te-bubble-button">
|
||||||
|
🔗 {form.buttonText}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="te-bubble-time">4:01 ✓✓</div>
|
<div className="te-bubble-time">4:01 ✓✓</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user