This commit is contained in:
parent
2644ebd0d3
commit
ecae73d5dd
@ -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
|
||||
══════════════════════════════════════════ */
|
||||
|
||||
@ -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>
|
||||
<>
|
||||
<div className="te-field">
|
||||
<label>{he.headerHandle}</label>
|
||||
<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>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user