Update template editor add photo
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
dvirlabs 2026-05-12 02:59:46 +03:00
parent 2644ebd0d3
commit ecae73d5dd
2 changed files with 155 additions and 10 deletions

View File

@ -190,6 +190,81 @@
line-height: 1.4;
}
/*
FILE UPLOAD
*/
.te-upload-container {
display: flex;
align-items: center;
gap: 0.75rem;
flex-wrap: wrap;
}
.te-upload-btn {
padding: 0.55rem 1rem;
background: linear-gradient(135deg, #25d366 0%, #1da851 100%);
color: #fff;
border: none;
border-radius: 7px;
font-size: 0.88rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s, transform 0.15s;
white-space: nowrap;
box-shadow: 0 2px 6px rgba(37, 211, 102, 0.25);
}
.te-upload-btn:hover:not(:disabled) {
opacity: 0.9;
transform: translateY(-1px);
}
.te-upload-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.te-upload-divider {
color: var(--color-text-secondary);
font-size: 0.85rem;
font-weight: 500;
}
.te-url-input {
flex: 1;
min-width: 200px;
padding: 0.55rem 0.75rem;
border: 1.5px solid var(--color-border);
border-radius: 7px;
font-size: 0.92rem;
background: var(--color-background);
color: var(--color-text);
font-family: inherit;
transition: border-color 0.15s, box-shadow 0.15s;
}
.te-url-input:focus {
outline: none;
border-color: #25d366;
box-shadow: 0 0 0 3px rgba(37, 211, 102, 0.12);
}
.te-image-preview {
margin-top: 0.75rem;
border: 1px solid var(--color-border);
border-radius: 8px;
overflow: hidden;
max-width: 100%;
}
.te-image-preview img {
width: 100%;
height: auto;
max-height: 250px;
object-fit: cover;
display: block;
}
/*
PARAM MAPPING
*/

View File

@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useRef } from 'react'
import { getWhatsAppTemplates, createWhatsAppTemplate, deleteWhatsAppTemplate } from '../api/api'
import { getWhatsAppTemplates, createWhatsAppTemplate, deleteWhatsAppTemplate, uploadImage } from '../api/api'
import './TemplateEditor.css'
// Param catalogue
@ -32,11 +32,13 @@ const he = {
buttonSection: 'כפתור (Button) — אופציונלי',
headerType: 'סוג כותרת',
headerText: 'טקסט הכותרת',
headerHandle: 'קישור/מזהה תמונה',
headerHandle: 'תמונה/קישור',
bodyText: 'טקסט ההודעה',
buttonType: 'סוג כפתור',
buttonText: 'טקסט הכפתור',
buttonUrl: 'כתובת URL',
uploadImage: 'העלה תמונה',
uploading: 'מעלה...',
paramMapping: 'מיפוי פרמטרים',
preview: 'תצוגה מקדימה',
save: 'שמור תבנית',
@ -47,7 +49,7 @@ const he = {
builtIn: 'מובנת',
chars: 'תווים',
headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת',
headerHandleHint: 'קישור לתמונה או מזהה Media שהועלה ל-Meta',
headerHandleHint: 'העלה תמונה או הדבק קישור לתמונה מ-Meta Media Library',
bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta',
buttonUrlHint: 'כתובת URL מלאה, לדוגמה: https://invy.dvirlabs.com',
keyHint: 'אותיות קטנות, מספרים ו-_ בלבד',
@ -98,8 +100,10 @@ export default function TemplateEditor({ onBack }) {
const [successMsg, setSuccessMsg] = useState('')
const [templates, setTemplates] = useState([])
const [loadingTpls, setLoadingTpls] = useState(true)
const [uploading, setUploading] = useState(false)
const isLoadingHeader = useRef(false)
const isLoadingBody = useRef(false)
const fileInputRef = useRef(null)
const loadTemplates = useCallback(() => {
setLoadingTpls(true)
@ -158,6 +162,38 @@ export default function TemplateEditor({ onBack }) {
return null
}
const handleFileUpload = async (e) => {
const file = e.target.files?.[0]
if (!file) return
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
if (!allowedTypes.includes(file.type)) {
setError('רק קבצי JPG, PNG, GIF ו-WebP מותרים')
return
}
// Validate file size (10MB)
if (file.size > 10 * 1024 * 1024) {
setError('גודל התמונה חייב להיות פחות מ-10MB')
return
}
setUploading(true)
setError('')
try {
const result = await uploadImage(file)
setForm(f => ({ ...f, headerHandle: result.url }))
setSuccessMsg('✓ התמונה הועלתה בהצלחה!')
setTimeout(() => setSuccessMsg(''), 3000)
} catch (err) {
setError(err?.response?.data?.detail || 'שגיאה בהעלאת התמונה')
} finally {
setUploading(false)
if (fileInputRef.current) fileInputRef.current.value = ''
}
}
const loadTemplateForEdit = (tpl) => {
isLoadingHeader.current = true
isLoadingBody.current = true
@ -334,13 +370,47 @@ export default function TemplateEditor({ onBack }) {
<small className="te-hint">{he.headerHint}</small>
</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 className="te-upload-container">
<input
type="file"
ref={fileInputRef}
onChange={handleFileUpload}
accept="image/jpeg,image/png,image/gif,image/webp"
disabled={uploading || saving}
style={{ display: 'none' }}
/>
<button
type="button"
className="te-upload-btn"
onClick={() => fileInputRef.current?.click()}
disabled={uploading || saving}
>
{uploading ? '⏳ מעלה...' : '📁 העלה תמונה'}
</button>
<span className="te-upload-divider">או</span>
<input
name="headerHandle"
value={form.headerHandle}
onChange={handleInput}
placeholder="https://example.com/image.jpg"
disabled={saving}
dir="ltr"
className="te-url-input"
/>
</div>
<small className="te-hint">{he.headerHandleHint}</small>
{form.headerHandle && (
<div className="te-image-preview">
<img src={form.headerHandle} alt="Header preview" onError={(e) => {
e.target.style.display = 'none'
}} />
</div>
)}
</div>
</>
)}
</div>