invy/frontend/src/components/GuestSelfService.jsx

295 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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