This commit is contained in:
parent
2644ebd0d3
commit
ecae73d5dd
@ -190,6 +190,81 @@
|
|||||||
line-height: 1.4;
|
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
|
PARAM MAPPING
|
||||||
══════════════════════════════════════════ */
|
══════════════════════════════════════════ */
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
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'
|
import './TemplateEditor.css'
|
||||||
|
|
||||||
// ── Param catalogue ───────────────────────────────────────────────────────────
|
// ── Param catalogue ───────────────────────────────────────────────────────────
|
||||||
@ -32,11 +32,13 @@ const he = {
|
|||||||
buttonSection: 'כפתור (Button) — אופציונלי',
|
buttonSection: 'כפתור (Button) — אופציונלי',
|
||||||
headerType: 'סוג כותרת',
|
headerType: 'סוג כותרת',
|
||||||
headerText: 'טקסט הכותרת',
|
headerText: 'טקסט הכותרת',
|
||||||
headerHandle: 'קישור/מזהה תמונה',
|
headerHandle: 'תמונה/קישור',
|
||||||
bodyText: 'טקסט ההודעה',
|
bodyText: 'טקסט ההודעה',
|
||||||
buttonType: 'סוג כפתור',
|
buttonType: 'סוג כפתור',
|
||||||
buttonText: 'טקסט הכפתור',
|
buttonText: 'טקסט הכפתור',
|
||||||
buttonUrl: 'כתובת URL',
|
buttonUrl: 'כתובת URL',
|
||||||
|
uploadImage: 'העלה תמונה',
|
||||||
|
uploading: 'מעלה...',
|
||||||
paramMapping: 'מיפוי פרמטרים',
|
paramMapping: 'מיפוי פרמטרים',
|
||||||
preview: 'תצוגה מקדימה',
|
preview: 'תצוגה מקדימה',
|
||||||
save: 'שמור תבנית',
|
save: 'שמור תבנית',
|
||||||
@ -47,7 +49,7 @@ const he = {
|
|||||||
builtIn: 'מובנת',
|
builtIn: 'מובנת',
|
||||||
chars: 'תווים',
|
chars: 'תווים',
|
||||||
headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת',
|
headerHint: 'השתמש ב-{{1}} לשם האורח — השאר ריק אם אין כותרת',
|
||||||
headerHandleHint: 'קישור לתמונה או מזהה Media שהועלה ל-Meta',
|
headerHandleHint: 'העלה תמונה או הדבק קישור לתמונה מ-Meta Media Library',
|
||||||
bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta',
|
bodyHint: 'השתמש ב-{{1}}, {{2}}, ... לפרמטרים — בדיוק כמו ב-Meta',
|
||||||
buttonUrlHint: 'כתובת URL מלאה, לדוגמה: https://invy.dvirlabs.com',
|
buttonUrlHint: 'כתובת URL מלאה, לדוגמה: https://invy.dvirlabs.com',
|
||||||
keyHint: 'אותיות קטנות, מספרים ו-_ בלבד',
|
keyHint: 'אותיות קטנות, מספרים ו-_ בלבד',
|
||||||
@ -98,8 +100,10 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
const [successMsg, setSuccessMsg] = useState('')
|
const [successMsg, setSuccessMsg] = useState('')
|
||||||
const [templates, setTemplates] = useState([])
|
const [templates, setTemplates] = useState([])
|
||||||
const [loadingTpls, setLoadingTpls] = useState(true)
|
const [loadingTpls, setLoadingTpls] = useState(true)
|
||||||
|
const [uploading, setUploading] = useState(false)
|
||||||
const isLoadingHeader = useRef(false)
|
const isLoadingHeader = useRef(false)
|
||||||
const isLoadingBody = useRef(false)
|
const isLoadingBody = useRef(false)
|
||||||
|
const fileInputRef = useRef(null)
|
||||||
|
|
||||||
const loadTemplates = useCallback(() => {
|
const loadTemplates = useCallback(() => {
|
||||||
setLoadingTpls(true)
|
setLoadingTpls(true)
|
||||||
@ -158,6 +162,38 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
return null
|
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) => {
|
const loadTemplateForEdit = (tpl) => {
|
||||||
isLoadingHeader.current = true
|
isLoadingHeader.current = true
|
||||||
isLoadingBody.current = true
|
isLoadingBody.current = true
|
||||||
@ -334,13 +370,47 @@ export default function TemplateEditor({ onBack }) {
|
|||||||
<small className="te-hint">{he.headerHint}</small>
|
<small className="te-hint">{he.headerHint}</small>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<div className="te-field">
|
<div className="te-field">
|
||||||
<label>{he.headerHandle}</label>
|
<label>{he.headerHandle}</label>
|
||||||
<input name="headerHandle" value={form.headerHandle}
|
<div className="te-upload-container">
|
||||||
onChange={handleInput} placeholder="https://example.com/image.jpg"
|
<input
|
||||||
disabled={saving} dir="ltr" />
|
type="file"
|
||||||
<small className="te-hint">{he.headerHandleHint}</small>
|
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>
|
</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>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user