diff --git a/frontend/src/components/TemplateEditor.css b/frontend/src/components/TemplateEditor.css index e176ed4..987c2e2 100644 --- a/frontend/src/components/TemplateEditor.css +++ b/frontend/src/components/TemplateEditor.css @@ -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 ══════════════════════════════════════════ */ diff --git a/frontend/src/components/TemplateEditor.jsx b/frontend/src/components/TemplateEditor.jsx index 942778d..6894e78 100644 --- a/frontend/src/components/TemplateEditor.jsx +++ b/frontend/src/components/TemplateEditor.jsx @@ -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 }) { {he.headerHint} ) : ( -