invy/frontend/src/components/EventForm.jsx
dvirlabs 2298143652
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Add scroll down to create event popup
2026-04-14 16:52:03 +03:00

234 lines
7.2 KiB
JavaScript

import { useEffect, useState, useRef } from 'react'
import { createEvent, uploadImage } from '../api/api'
import './EventForm.css'
const he = {
createNewEvent: 'צור אירוע חדש',
eventNameRequired: 'שם האירוע נדרש',
failedCreate: 'נכשל בהוספת אירוע',
eventName: 'שם האירוע',
eventDate: 'תאריך',
location: 'מיקום',
invitationImage: 'תמונת הזמנה (רקע)',
invitationImageHint: 'העלה תמונה או הדבק קישור לתמונה שתשמש כרקע להזמנה',
uploadImage: '📁 העלה תמונה',
uploading: 'מעלה...',
orPasteUrl: 'או הדבק כתובת URL:',
create: 'צור',
cancel: 'ביטול',
guestFormFields: 'שדות שיוצגו לאורח בדף ה-RSVP',
}
const GUEST_FIELD_OPTIONS = [
{ key: 'mealPref', label: 'העדפת ארוחה' },
{ key: 'companions', label: 'כמה תהיו? (מספר מגיעים)' },
]
function EventForm({ onEventCreated, onCancel }) {
const [formData, setFormData] = useState({
name: '',
date: '',
location: '',
invitation_image_url: ''
})
// Which fields the guest sees on the RSVP page — default: both shown
const [guestFields, setGuestFields] = useState(new Set(['mealPref', 'companions']))
const toggleGuestField = (key) => {
setGuestFields(prev => {
const next = new Set(prev)
next.has(key) ? next.delete(key) : next.add(key)
return next
})
}
const [loading, setLoading] = useState(false)
const [uploading, setUploading] = useState(false)
const [error, setError] = useState('')
const fileInputRef = useRef(null)
useEffect(() => {
const previousOverflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
return () => {
document.body.style.overflow = previousOverflow
}
}, [])
const handleChange = (e) => {
const { name, value } = e.target
setFormData(prev => ({
...prev,
[name]: value
}))
}
const handleFileChange = async (e) => {
const file = e.target.files[0]
if (!file) return
setUploading(true)
setError('')
try {
const result = await uploadImage(file)
setFormData(prev => ({ ...prev, invitation_image_url: result.url }))
} catch (err) {
setError(err.response?.data?.detail || 'נכשל בהעלאת התמונה')
} finally {
setUploading(false)
}
}
const handleSubmit = async (e) => {
e.preventDefault()
if (!formData.name.trim()) {
setError(he.eventNameRequired)
return
}
setLoading(true)
setError('')
try {
const payload = { ...formData, guest_form_fields: JSON.stringify([...guestFields]) }
const newEvent = await createEvent(payload)
setFormData({ name: '', date: '', location: '', invitation_image_url: '' })
setGuestFields(new Set(['mealPref', 'companions']))
onEventCreated(newEvent)
} catch (err) {
setError(err.response?.data?.detail || he.failedCreate)
} finally {
setLoading(false)
}
}
return (
<div className="event-form-container">
<div
className="event-form-overlay"
onMouseDown={(e) => {
e.preventDefault()
e.stopPropagation()
onCancel()
}}
></div>
<div className="event-form" onMouseDown={(e) => e.stopPropagation()}>
<h2>{he.createNewEvent}</h2>
{error && <div className="error-message">{error}</div>}
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="name">{he.eventName} *</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleChange}
placeholder="לדוגמה: חתונה, יום הולדת, מפגש"
required
/>
</div>
<div className="form-group">
<label htmlFor="date">{he.eventDate}</label>
<input
type="datetime-local"
id="date"
name="date"
value={formData.date}
onChange={handleChange}
/>
</div>
<div className="form-group">
<label htmlFor="location">{he.location}</label>
<input
type="text"
id="location"
name="location"
value={formData.location}
onChange={handleChange}
placeholder="לדוגמה: תל אביב, ישראל"
/>
</div>
<div className="form-group">
<label>{he.invitationImage}</label>
<div className="image-upload-area">
<input
type="file"
ref={fileInputRef}
accept="image/jpeg,image/png,image/gif,image/webp"
style={{ display: 'none' }}
onChange={handleFileChange}
/>
<button
type="button"
className="btn-upload-image"
onClick={() => fileInputRef.current.click()}
disabled={uploading}
>
{uploading ? he.uploading : he.uploadImage}
</button>
{formData.invitation_image_url && (
<button
type="button"
className="btn-remove-image"
onClick={() => setFormData(prev => ({ ...prev, invitation_image_url: '' }))}
>
</button>
)}
</div>
<small className="form-hint">{he.invitationImageHint}</small>
<label className="url-input-label">{he.orPasteUrl}</label>
<input
type="url"
name="invitation_image_url"
value={formData.invitation_image_url}
onChange={handleChange}
placeholder="https://example.com/invitation.jpg"
/>
{formData.invitation_image_url && (
<div className="image-preview">
<img
src={formData.invitation_image_url}
alt="תצוגה מקדימה"
onError={(e) => { e.target.style.display = 'none' }}
/>
</div>
)}
</div>
<div className="form-group">
<label>{he.guestFormFields}</label>
<div className="guest-fields-list">
{GUEST_FIELD_OPTIONS.map(opt => (
<label key={opt.key} className="guest-field-checkbox">
<input
type="checkbox"
checked={guestFields.has(opt.key)}
onChange={() => toggleGuestField(opt.key)}
/>
{opt.label}
</label>
))}
</div>
</div>
<div className="form-actions">
<button type="button" onClick={onCancel} className="btn-cancel">
{he.cancel}
</button>
<button type="submit" disabled={loading} className="btn-submit">
{loading ? 'יוצר...' : he.create}
</button>
</div>
</form>
</div>
</div>
)
}
export default EventForm