295 lines
11 KiB
JavaScript
295 lines
11 KiB
JavaScript
import { useState, useEffect } from 'react'
|
||
import { getPublicEvent, getGuestForEvent, submitEventRsvp } from '../api/api'
|
||
import './GuestSelfService.css'
|
||
|
||
/**
|
||
* GuestSelfService
|
||
*
|
||
* Primary flow : guest opens /guest/:eventId (from WhatsApp button)
|
||
* → page loads event details
|
||
* → guest enters phone number
|
||
* → backend looks up guest scoped to THAT event
|
||
* → guest fills RSVP form
|
||
* → POST /public/events/:eventId/rsvp (only updates this event's record)
|
||
*
|
||
* Fallback flow : /guest with no eventId → plain phone lookup (legacy)
|
||
*/
|
||
function GuestSelfService({ eventId }) {
|
||
// ─── Event state ──────────────────────────────────────────────────────
|
||
const [event, setEvent] = useState(null)
|
||
const [eventLoading, setEventLoading] = useState(false)
|
||
const [eventError, setEventError] = useState('')
|
||
|
||
// ─── Phone lookup state ──────────────────────────────────────────────
|
||
const [phoneNumber, setPhoneNumber] = useState('')
|
||
const [guest, setGuest] = useState(null)
|
||
|
||
// ─── RSVP form state ─────────────────────────────────────────────────
|
||
const [loading, setLoading] = useState(false)
|
||
const [error, setError] = useState('')
|
||
const [success, setSuccess] = useState(false)
|
||
const [formData, setFormData] = useState({
|
||
first_name: '',
|
||
last_name: '',
|
||
rsvp_status: 'invited',
|
||
meal_preference: '',
|
||
has_plus_one: false,
|
||
plus_one_name: '',
|
||
})
|
||
|
||
// ─── Load event on mount ────────────────────────────────────────────
|
||
useEffect(() => {
|
||
if (!eventId) return
|
||
setEventLoading(true)
|
||
getPublicEvent(eventId)
|
||
.then(setEvent)
|
||
.catch(() => setEventError('האירוע לא נמצא.'))
|
||
.finally(() => setEventLoading(false))
|
||
}, [eventId])
|
||
|
||
// ─── Phone lookup ────────────────────────────────────────────────────
|
||
const handleLookup = async (e) => {
|
||
e.preventDefault()
|
||
setError('')
|
||
setLoading(true)
|
||
try {
|
||
const guestData = await getGuestForEvent(eventId, phoneNumber)
|
||
// Always present the form regardless of whether the guest was pre-imported.
|
||
// Never pre-fill the name — the host may have saved a nickname in their
|
||
// contacts that the guest should not see.
|
||
setGuest(guestData) // found:true or found:false — both show the RSVP form
|
||
setFormData({
|
||
first_name: '', // guest enters their own name
|
||
last_name: '',
|
||
rsvp_status: guestData.rsvp_status || 'invited',
|
||
meal_preference: guestData.meal_preference || '',
|
||
has_plus_one: guestData.has_plus_one || false,
|
||
plus_one_name: guestData.plus_one_name || '',
|
||
})
|
||
} catch {
|
||
// Only real network / server errors reach here
|
||
setError('אירעה שגיאה. אנא נסה שוב.')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
// ─── Submit RSVP ─────────────────────────────────────────────────────
|
||
const handleSubmit = async (e) => {
|
||
e.preventDefault()
|
||
setError('')
|
||
setLoading(true)
|
||
try {
|
||
await submitEventRsvp(eventId, { phone: phoneNumber, ...formData })
|
||
setSuccess(true)
|
||
} catch {
|
||
setError('נכשל בשמירת הפרטים. אנא נסה שוב.')
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleChange = (e) => {
|
||
const { name, value, type, checked } = e.target
|
||
setFormData((prev) => ({ ...prev, [name]: type === 'checkbox' ? checked : value }))
|
||
}
|
||
|
||
// ─── RSVP form (shared JSX) ──────────────────────────────────────────
|
||
const rsvpForm = (
|
||
<form onSubmit={handleSubmit} className="update-form">
|
||
<div className="form-group">
|
||
<label htmlFor="first_name">שם פרטי *</label>
|
||
<input
|
||
type="text"
|
||
id="first_name"
|
||
name="first_name"
|
||
value={formData.first_name}
|
||
onChange={handleChange}
|
||
placeholder="השם הפרטי שלך"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="last_name">שם משפחה</label>
|
||
<input
|
||
type="text"
|
||
id="last_name"
|
||
name="last_name"
|
||
value={formData.last_name}
|
||
onChange={handleChange}
|
||
placeholder="שם המשפחה שלך"
|
||
/>
|
||
</div>
|
||
|
||
<div className="form-group">
|
||
<label htmlFor="rsvp_status">סטטוס אישור הגעה *</label>
|
||
<select
|
||
id="rsvp_status"
|
||
name="rsvp_status"
|
||
value={formData.rsvp_status}
|
||
onChange={handleChange}
|
||
required
|
||
>
|
||
<option value="invited">עדיין לא בטוח</option>
|
||
<option value="confirmed">כן, אהיה שם! 🎉</option>
|
||
<option value="declined">מצטער, לא אוכל להגיע 😢</option>
|
||
</select>
|
||
</div>
|
||
|
||
{formData.rsvp_status === 'confirmed' && (
|
||
<>
|
||
<div className="form-group">
|
||
<label htmlFor="meal_preference">העדפת ארוחה</label>
|
||
<select
|
||
id="meal_preference"
|
||
name="meal_preference"
|
||
value={formData.meal_preference}
|
||
onChange={handleChange}
|
||
>
|
||
<option value="">בחר ארוחה</option>
|
||
<option value="chicken">עוף</option>
|
||
<option value="beef">בשר בקר</option>
|
||
<option value="fish">דג</option>
|
||
<option value="vegetarian">צמחוני</option>
|
||
<option value="vegan">טבעוני</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="form-group checkbox-group">
|
||
<label>
|
||
<input
|
||
type="checkbox"
|
||
name="has_plus_one"
|
||
checked={formData.has_plus_one}
|
||
onChange={handleChange}
|
||
/>
|
||
מביא פלאס ואן
|
||
</label>
|
||
</div>
|
||
|
||
{formData.has_plus_one && (
|
||
<div className="form-group">
|
||
<label htmlFor="plus_one_name">שם הפלאס ואן</label>
|
||
<input
|
||
type="text"
|
||
id="plus_one_name"
|
||
name="plus_one_name"
|
||
value={formData.plus_one_name}
|
||
onChange={handleChange}
|
||
placeholder="שם מלא של האורח"
|
||
/>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
|
||
<button type="submit" disabled={loading} className="btn btn-primary">
|
||
{loading ? 'שומר...' : 'שמור אישור הגעה'}
|
||
</button>
|
||
</form>
|
||
)
|
||
|
||
// ─── Early returns ─────────────────────────────────────────────────────
|
||
|
||
if (eventId && eventLoading) {
|
||
return (
|
||
<div className="guest-self-service" dir="rtl">
|
||
<div className="service-container">
|
||
<p className="subtitle">טוען פרטי אירוע...</p>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (eventId && eventError) {
|
||
return (
|
||
<div className="guest-self-service" dir="rtl">
|
||
<div className="service-container">
|
||
<h1>💒 אישור הגעה</h1>
|
||
<div className="error-message">{eventError}</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// ─── Event header (shown when we have event details) ─────────────────
|
||
const eventHeader = event ? (
|
||
<>
|
||
<h1>💒 {event.name}</h1>
|
||
{(event.partner1_name || event.partner2_name) && (
|
||
<p className="subtitle">
|
||
{[event.partner1_name, event.partner2_name].filter(Boolean).join(' ו')}
|
||
</p>
|
||
)}
|
||
{event.date && <p className="subtitle">📅 {event.date}</p>}
|
||
{event.venue && <p className="subtitle">📍 {event.venue}</p>}
|
||
{event.event_time && <p className="subtitle">⏰ {event.event_time}</p>}
|
||
</>
|
||
) : (
|
||
<>
|
||
<h1>💒 אישור הגעה לחתונה</h1>
|
||
<p className="subtitle">עדכן את הגעתך והעדפותיך</p>
|
||
</>
|
||
)
|
||
|
||
// ─── Main render ──────────────────────────────────────────────────────
|
||
return (
|
||
<div className="guest-self-service" dir="rtl">
|
||
<div className="service-container">
|
||
{eventHeader}
|
||
|
||
{!guest ? (
|
||
/* ── Step 1: phone lookup ── */
|
||
<form onSubmit={handleLookup} className="lookup-form">
|
||
<div className="form-group">
|
||
<label htmlFor="phone">הזן מספר טלפון לאימות זהות</label>
|
||
<input
|
||
type="tel"
|
||
id="phone"
|
||
value={phoneNumber}
|
||
onChange={(e) => setPhoneNumber(e.target.value)}
|
||
placeholder="לדוגמה: 0501234567"
|
||
required
|
||
/>
|
||
</div>
|
||
|
||
{error && <div className="error-message">{error}</div>}
|
||
|
||
<button type="submit" disabled={loading} className="btn btn-primary">
|
||
{loading ? 'מחפש...' : 'מצא את ההזמנה שלי'}
|
||
</button>
|
||
</form>
|
||
) : (
|
||
/* ── Step 2: RSVP form ── */
|
||
<div className="update-form-container">
|
||
<div className="guest-info">
|
||
<h2>שלום! 👋</h2>
|
||
<p className="guest-note">אנא אשר את הגעתך והעדפותיך</p>
|
||
{!success && (
|
||
<button
|
||
onClick={() => { setGuest(null); setError(''); setSuccess(false) }}
|
||
className="btn-link"
|
||
>
|
||
מספר טלפון אחר?
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
{success && (
|
||
<div className="success-message">
|
||
✓ תודה! אישור ההגעה שלך נשמר בהצלחה 🎉
|
||
</div>
|
||
)}
|
||
{error && <div className="error-message">{error}</div>}
|
||
{!success && rsvpForm}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
export default GuestSelfService
|
||
|