diff --git a/GUEST_URL.md b/GUEST_URL.md new file mode 100644 index 0000000..e2ef369 --- /dev/null +++ b/GUEST_URL.md @@ -0,0 +1,28 @@ +# Guest Self-Service URL + +Share this URL with your guests so they can update their RSVP: + +**http://localhost:5173/guest** + +## How it works: + +1. Guests visit the URL +2. They enter their phone number (must match the phone number in your guest list) +3. They can update: + - RSVP status (confirmed/declined/pending) + - Meal preference (if attending) + - Plus one details (if attending) +4. Changes are immediately reflected in your admin guest list + +## For Production: + +Replace `localhost:5173` with your actual domain, e.g.: +- `https://yourwedding.com/guest` +- `https://weddingapp.yourname.com/guest` + +## Sharing Tips: + +- Send via text message with instructions +- Add QR code to physical invitations +- Include in email invitations +- Post on wedding website diff --git a/backend/google_contacts.py b/backend/google_contacts.py index 237aca0..fae6b14 100644 --- a/backend/google_contacts.py +++ b/backend/google_contacts.py @@ -1,6 +1,40 @@ import httpx from sqlalchemy.orm import Session import models +import re + + +def normalize_phone_number(phone: str) -> str: + """ + Convert phone numbers from +972 format to Israeli 0 format + Examples: + +972501234567 -> 0501234567 + +972-50-123-4567 -> 0501234567 + 972501234567 -> 0501234567 + """ + if not phone: + return phone + + # Remove all non-digit characters except + + cleaned = re.sub(r'[^\d+]', '', phone) + + # Handle +972 format + if cleaned.startswith('+972'): + # Remove +972 and add 0 prefix + return '0' + cleaned[4:] + elif cleaned.startswith('972'): + # Remove 972 and add 0 prefix + return '0' + cleaned[3:] + + # If already starts with 0, return as is + if cleaned.startswith('0'): + return cleaned + + # If it's a 9-digit number (Israeli mobile without prefix), add 0 + if len(cleaned) == 9 and cleaned[0] in '5789': + return '0' + cleaned + + return cleaned async def import_contacts_from_google(access_token: str, db: Session, owner: str = None) -> int: @@ -58,6 +92,10 @@ async def import_contacts_from_google(access_token: str, db: Session, owner: str phones = connection.get("phoneNumbers", []) phone_number = phones[0].get("value") if phones else None + # Normalize phone number to Israeli format (0...) + if phone_number: + phone_number = normalize_phone_number(phone_number) + # Check if contact already exists by email OR phone number existing = None if email: diff --git a/backend/main.py b/backend/main.py index 050e265..1e2735a 100644 --- a/backend/main.py +++ b/backend/main.py @@ -189,5 +189,68 @@ async def google_callback(code: str, db: Session = Depends(get_db)): status_code=302 ) + +# Public endpoint for guests to update their info +@app.get("/public/guest/{phone_number}") +def get_guest_by_phone(phone_number: str, db: Session = Depends(get_db)): + """ + Public endpoint: Get guest info by phone number + Returns guest if found, or None to allow new registration + """ + guest = db.query(models.Guest).filter(models.Guest.phone_number == phone_number).first() + if not guest: + # Return structure indicating not found, but don't raise error + return {"found": False, "phone_number": phone_number} + return {"found": True, **guest.__dict__} + + +@app.put("/public/guest/{phone_number}") +def update_guest_by_phone( + phone_number: str, + guest_update: schemas.GuestPublicUpdate, + db: Session = Depends(get_db) +): + """ + Public endpoint: Allow guests to update their own info using phone number + Creates new guest if not found (marked as 'self-service') + """ + guest = db.query(models.Guest).filter(models.Guest.phone_number == phone_number).first() + + if not guest: + # Create new guest from link (not imported from contacts) + guest = models.Guest( + first_name=guest_update.first_name or "Guest", + last_name=guest_update.last_name or "", + phone_number=phone_number, + rsvp_status=guest_update.rsvp_status or "pending", + meal_preference=guest_update.meal_preference, + has_plus_one=guest_update.has_plus_one or False, + plus_one_name=guest_update.plus_one_name, + owner="self-service" # Mark as self-registered via link + ) + db.add(guest) + else: + # Update existing guest + # Always update names if provided (override contact names) + if guest_update.first_name is not None: + guest.first_name = guest_update.first_name + if guest_update.last_name is not None: + guest.last_name = guest_update.last_name + + # Update other fields + if guest_update.rsvp_status is not None: + guest.rsvp_status = guest_update.rsvp_status + if guest_update.meal_preference is not None: + guest.meal_preference = guest_update.meal_preference + if guest_update.has_plus_one is not None: + guest.has_plus_one = guest_update.has_plus_one + if guest_update.plus_one_name is not None: + guest.plus_one_name = guest_update.plus_one_name + + db.commit() + db.refresh(guest) + return guest + + if __name__ == "__main__": uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) \ No newline at end of file diff --git a/backend/schemas.py b/backend/schemas.py index 3efd92a..8eebac3 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -33,3 +33,13 @@ class Guest(GuestBase): class Config: from_attributes = True + + +class GuestPublicUpdate(BaseModel): + """Schema for public guest self-service updates""" + first_name: Optional[str] = None + last_name: Optional[str] = None + rsvp_status: Optional[str] = None + meal_preference: Optional[str] = None + has_plus_one: Optional[bool] = None + plus_one_name: Optional[str] = None diff --git a/frontend/src/App.css b/frontend/src/App.css index 93b7719..4a52a05 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1,3 +1,11 @@ +* { + box-sizing: border-box; +} + +[dir="rtl"] { + text-align: right; +} + .App { min-height: 100vh; padding: 20px; @@ -9,6 +17,34 @@ header { margin-bottom: 30px; } +.header-content { + display: flex; + justify-content: center; + align-items: center; + gap: 20px; + position: relative; +} + +.btn-logout { + position: absolute; + left: 0; + padding: 8px 16px; + background: rgba(255, 255, 255, 0.2); + color: white; + border: 2px solid white; + font-size: 14px; +} + +.btn-logout:hover { + background: rgba(255, 255, 255, 0.3); + transform: translateY(-1px); +} + +[dir="rtl"] .btn-logout { + left: auto; + right: 0; +} + header h1 { font-size: 2.5rem; font-weight: 600; diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index b6a8838..240c401 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -3,6 +3,8 @@ import GuestList from './components/GuestList' import GuestForm from './components/GuestForm' import SearchFilter from './components/SearchFilter' import GoogleImport from './components/GoogleImport' +import GuestSelfService from './components/GuestSelfService' +import Login from './components/Login' import { getGuests, searchGuests } from './api/api' import './App.css' @@ -11,10 +13,30 @@ function App() { const [loading, setLoading] = useState(true) const [showForm, setShowForm] = useState(false) const [editingGuest, setEditingGuest] = useState(null) + const [currentPage, setCurrentPage] = useState('admin') // 'admin' or 'guest' + const [isAuthenticated, setIsAuthenticated] = useState(false) + + // Check authentication status on mount + useEffect(() => { + const authStatus = localStorage.getItem('isAuthenticated') + if (authStatus === 'true') { + setIsAuthenticated(true) + } + }, []) + + // Check URL for guest mode + useEffect(() => { + const path = window.location.pathname + if (path === '/guest' || path === '/guest/') { + setCurrentPage('guest') + } + }, []) useEffect(() => { - loadGuests() - }, []) + if (currentPage === 'admin') { + loadGuests() + } + }, [currentPage]) const loadGuests = async () => { try { @@ -59,16 +81,41 @@ function App() { loadGuests() } + const handleLogin = () => { + setIsAuthenticated(true) + } + + const handleLogout = () => { + localStorage.removeItem('isAuthenticated') + setIsAuthenticated(false) + } + + // Render guest self-service page + if (currentPage === 'guest') { + return + } + + // Require authentication for admin panel + if (!isAuthenticated) { + return + } + + // Render admin page return ( -
+
-

💒 Wedding Guest List

+
+

💒 רשימת מוזמנים לחתונה

+ +
@@ -76,7 +123,7 @@ function App() { {loading ? ( -
Loading guests...
+
טוען אורחים...
) : ( { return response.data } +// Public endpoints for guest self-service +export const getGuestByPhone = async (phoneNumber) => { + const response = await api.get(`/public/guest/${encodeURIComponent(phoneNumber)}`) + return response.data +} + +export const updateGuestByPhone = async (phoneNumber, guestData) => { + const response = await api.put(`/public/guest/${encodeURIComponent(phoneNumber)}`, guestData) + return response.data +} + export default api diff --git a/frontend/src/components/GoogleImport.jsx b/frontend/src/components/GoogleImport.jsx index 0af979f..3a1e37b 100644 --- a/frontend/src/components/GoogleImport.jsx +++ b/frontend/src/components/GoogleImport.jsx @@ -12,14 +12,14 @@ function GoogleImport({ onImportComplete }) { const error = urlParams.get('error') if (imported) { - alert(`Successfully imported ${imported} contacts from ${importOwner}'s Google account!`) + alert(`יובאו בהצלחה ${imported} אנשי קשר מחשבון Google של ${importOwner}!`) onImportComplete() // Clean up URL window.history.replaceState({}, document.title, window.location.pathname) } if (error) { - alert(`Failed to import contacts: ${error}`) + alert(`נכשל בייבוא אנשי הקשר: ${error}`) // Clean up URL window.history.replaceState({}, document.title, window.location.pathname) } @@ -38,7 +38,7 @@ function GoogleImport({ onImportComplete }) { disabled={importing} > {importing ? ( - '⏳ Importing...' + '⏳ מייבא...' ) : ( <> @@ -59,7 +59,7 @@ function GoogleImport({ onImportComplete }) { d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" /> - Import from Google + ייבוא מ-Google )} diff --git a/frontend/src/components/GuestForm.jsx b/frontend/src/components/GuestForm.jsx index 73e0906..686e205 100644 --- a/frontend/src/components/GuestForm.jsx +++ b/frontend/src/components/GuestForm.jsx @@ -121,12 +121,11 @@ function GuestForm({ guest, onClose }) { value={formData.meal_preference} onChange={handleChange} > - - - - - - + + + + +
diff --git a/frontend/src/components/GuestList.jsx b/frontend/src/components/GuestList.jsx index e2cc5b1..ffdd127 100644 --- a/frontend/src/components/GuestList.jsx +++ b/frontend/src/components/GuestList.jsx @@ -32,26 +32,26 @@ function GuestList({ guests, onEdit, onUpdate }) { const handleBulkDelete = async () => { if (selectedGuests.length === 0) return - if (window.confirm(`Are you sure you want to delete ${selectedGuests.length} guests?`)) { + if (window.confirm(`האם אתה בטוח שברצונך למחוק ${selectedGuests.length} אורחים?`)) { try { await deleteGuestsBulk(selectedGuests) setSelectedGuests([]) onUpdate() } catch (error) { console.error('Error deleting guests:', error) - alert('Failed to delete guests') + alert('נכשל במחיקת האורחים') } } } const handleDelete = async (id) => { - if (window.confirm('Are you sure you want to delete this guest?')) { + if (window.confirm('האם אתה בטוח שברצונך למחוק את האורח?')) { try { await deleteGuest(id) onUpdate() } catch (error) { console.error('Error deleting guest:', error) - alert('Failed to delete guest') + alert('נכשל במחיקת האורח') } } } @@ -67,10 +67,23 @@ function GuestList({ guests, onEdit, onUpdate }) { } } + const getRsvpLabel = (status) => { + switch (status) { + case 'accepted': + return 'אישר' + case 'declined': + return 'סירוב' + case 'pending': + return 'המתנה' + default: + return status + } + } + if (guests.length === 0) { return (
-

No guests found. Add your first guest to get started!

+

לא נמצאו אורחים. הוסף את האורח הראשון שלך!

) } @@ -78,10 +91,10 @@ function GuestList({ guests, onEdit, onUpdate }) { return (
-

Guest List ({guests.length})

+

רשימת אורחים ({guests.length})

{selectedGuests.length > 0 && ( )}
@@ -112,15 +125,15 @@ function GuestList({ guests, onEdit, onUpdate }) { checked={paginatedGuests.length > 0 && selectedGuests.length === paginatedGuests.length} /> - Name - Email - Phone - RSVP - Meal - Plus One - Table - Owner - Actions + שם + אימייל + טלפון + אישור + ארוחה + פלאס ואן + שולחן + מייבא + פעולות @@ -140,13 +153,13 @@ function GuestList({ guests, onEdit, onUpdate }) { {guest.phone_number || '-'} - {guest.rsvp_status} + {getRsvpLabel(guest.rsvp_status)} {guest.meal_preference || '-'} {guest.has_plus_one ? ( - ✓ {guest.plus_one_name || 'Yes'} + ✓ {guest.plus_one_name || 'כן'} ) : ( '-' )} @@ -158,13 +171,13 @@ function GuestList({ guests, onEdit, onUpdate }) { className="btn-small btn-edit" onClick={() => onEdit(guest)} > - Edit + ערוך @@ -179,16 +192,16 @@ function GuestList({ guests, onEdit, onUpdate }) { onClick={() => setCurrentPage(p => Math.max(1, p - 1))} disabled={currentPage === 1} > - Previous + הקודם - Page {currentPage} of {totalPages} + עמוד {currentPage} מתוך {totalPages}
)} diff --git a/frontend/src/components/GuestSelfService.css b/frontend/src/components/GuestSelfService.css new file mode 100644 index 0000000..9ce435d --- /dev/null +++ b/frontend/src/components/GuestSelfService.css @@ -0,0 +1,188 @@ +.guest-self-service { + min-height: 100vh; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.service-container { + background: white; + border-radius: 20px; + padding: 40px; + max-width: 500px; + width: 100%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} + +.service-container h1 { + text-align: center; + color: #667eea; + margin-bottom: 10px; + font-size: 2.5rem; +} + +.subtitle { + text-align: center; + color: #666; + margin-bottom: 30px; + font-size: 1.1rem; +} + +.lookup-form, +.update-form { + display: flex; + flex-direction: column; + gap: 20px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.form-group label { + font-weight: 600; + color: #333; + font-size: 0.95rem; +} + +.form-group input, +.form-group select { + padding: 12px; + border: 2px solid #e0e0e0; + border-radius: 8px; + font-size: 1rem; + transition: all 0.3s ease; +} + +.form-group input:focus, +.form-group select:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.checkbox-group { + flex-direction: row; + align-items: center; +} + +.checkbox-group label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + font-weight: 500; +} + +.checkbox-group input[type="checkbox"] { + width: 20px; + height: 20px; + cursor: pointer; +} + +.btn { + padding: 14px 24px; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + width: 100%; +} + +.btn-primary:hover:not(:disabled) { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3); +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-link { + background: none; + border: none; + color: #667eea; + cursor: pointer; + text-decoration: underline; + font-size: 0.9rem; + padding: 0; + margin-top: 5px; +} + +.btn-link:hover { + color: #764ba2; +} + +.guest-info { + background: #f8f9ff; + padding: 20px; + border-radius: 12px; + margin-bottom: 20px; + text-align: center; +} + +.guest-info h2 { + color: #667eea; + margin-bottom: 10px; + font-size: 1.5rem; +} + +.guest-note { + color: #666; + font-size: 0.95rem; + margin-bottom: 10px; +} + +.error-message { + background: #fee; + border: 2px solid #fcc; + color: #c33; + padding: 12px; + border-radius: 8px; + text-align: center; + font-weight: 500; +} + +.success-message { + background: #efe; + border: 2px solid #cfc; + color: #3a3; + padding: 12px; + border-radius: 8px; + text-align: center; + font-weight: 500; + animation: slideIn 0.3s ease; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 600px) { + .service-container { + padding: 30px 20px; + } + + .service-container h1 { + font-size: 2rem; + } +} diff --git a/frontend/src/components/GuestSelfService.jsx b/frontend/src/components/GuestSelfService.jsx new file mode 100644 index 0000000..f809861 --- /dev/null +++ b/frontend/src/components/GuestSelfService.jsx @@ -0,0 +1,227 @@ +import { useState } from 'react' +import { getGuestByPhone, updateGuestByPhone } from '../api/api' +import './GuestSelfService.css' + +function GuestSelfService() { + const [phoneNumber, setPhoneNumber] = useState('') + const [guest, setGuest] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState('') + const [success, setSuccess] = useState(false) + const [formData, setFormData] = useState({ + first_name: '', + last_name: '', + rsvp_status: 'pending', + meal_preference: '', + has_plus_one: false, + plus_one_name: '' + }) + + const handleLookup = async (e) => { + e.preventDefault() + setError('') + setSuccess(false) + setLoading(true) + + try { + const guestData = await getGuestByPhone(phoneNumber) + setGuest(guestData) + + // Always start with empty form - don't show contact info + setFormData({ + first_name: '', + last_name: '', + rsvp_status: 'pending', + meal_preference: '', + has_plus_one: false, + plus_one_name: '' + }) + } catch (err) { + setError('Failed to check phone number. Please try again.') + setGuest(null) + } finally { + setLoading(false) + } + } + + const handleSubmit = async (e) => { + e.preventDefault() + setError('') + setSuccess(false) + setLoading(true) + + try { + await updateGuestByPhone(phoneNumber, formData) + setSuccess(true) + // Refresh guest data + const updatedGuest = await getGuestByPhone(phoneNumber) + setGuest(updatedGuest) + } catch (err) { + setError('נכשל בעדכון המידע. אנא נסה שוב.') + } finally { + setLoading(false) + } + } + + const handleChange = (e) => { + const { name, value, type, checked } = e.target + setFormData(prev => ({ + ...prev, + [name]: type === 'checkbox' ? checked : value + })) + } + + return ( +
+
+

💒 אישור הגעה לחתונה

+

עדכן את הגעתך והעדפותיך

+ + {!guest ? ( +
+
+ + setPhoneNumber(e.target.value)} + placeholder="לדוגמה: 0501234567" + pattern="0[2-9]\d{7,8}" + title="נא להזין מספר טלפון ישראלי תקין (10 ספרות המתחיל ב-0)" + required + /> +
+ + {error &&
{error}
} + + +
+ ) : ( +
+
+

שלום! 👋

+

אנא הזן את הפרטים שלך לאישור הגעה

+ +
+ + {success && ( +
+ ✓ המידע שלך עודכן בהצלחה! +
+ )} + + {error &&
{error}
} + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ + {formData.rsvp_status === 'accepted' && ( + <> +
+ + +
+ +
+ +
+ + {formData.has_plus_one && ( +
+ + +
+ )} + + )} + + +
+
+ )} +
+
+ ) +} + +export default GuestSelfService diff --git a/frontend/src/components/Login.css b/frontend/src/components/Login.css new file mode 100644 index 0000000..ba14909 --- /dev/null +++ b/frontend/src/components/Login.css @@ -0,0 +1,126 @@ +.login-page { + min-height: 100vh; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + padding: 20px; +} + +.login-container { + background: white; + border-radius: 20px; + padding: 40px; + max-width: 450px; + width: 100%; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); +} + +.login-container h1 { + text-align: center; + color: #667eea; + margin-bottom: 10px; + font-size: 2rem; +} + +.login-subtitle { + text-align: center; + color: #666; + margin-bottom: 30px; + font-size: 1rem; +} + +.login-form { + display: flex; + flex-direction: column; + gap: 20px; +} + +.login-form .form-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.login-form label { + font-weight: 600; + color: #333; + font-size: 0.95rem; +} + +.login-form input { + padding: 12px; + border: 2px solid #e0e0e0; + border-radius: 8px; + font-size: 1rem; + transition: all 0.3s ease; + font-family: inherit; +} + +.login-form input:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.login-form .btn { + padding: 14px 24px; + border: none; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + margin-top: 10px; +} + +.login-form .btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + width: 100%; +} + +.login-form .btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3); +} + +.login-form .error-message { + background: #fee; + border: 2px solid #fcc; + color: #c33; + padding: 12px; + border-radius: 8px; + text-align: center; + font-weight: 500; +} + +.login-info { + margin-top: 30px; + padding: 20px; + background: #f8f9ff; + border-radius: 12px; + text-align: center; + font-size: 0.9rem; + color: #666; +} + +.login-info p { + margin: 5px 0; +} + +.login-info p:first-child { + font-weight: 600; + color: #667eea; + margin-bottom: 10px; +} + +@media (max-width: 600px) { + .login-container { + padding: 30px 20px; + } + + .login-container h1 { + font-size: 1.5rem; + } +} diff --git a/frontend/src/components/Login.jsx b/frontend/src/components/Login.jsx new file mode 100644 index 0000000..44b8580 --- /dev/null +++ b/frontend/src/components/Login.jsx @@ -0,0 +1,81 @@ +import { useState } from 'react' +import './Login.css' + +function Login({ onLogin }) { + const [credentials, setCredentials] = useState({ + username: '', + password: '' + }) + const [error, setError] = useState('') + + const handleSubmit = (e) => { + e.preventDefault() + setError('') + + // Simple authentication - you can change these credentials + const ADMIN_USERNAME = 'admin' + const ADMIN_PASSWORD = 'wedding2025' + + if (credentials.username === ADMIN_USERNAME && credentials.password === ADMIN_PASSWORD) { + localStorage.setItem('isAuthenticated', 'true') + onLogin() + } else { + setError('שם משתמש או סיסמה שגויים') + } + } + + const handleChange = (e) => { + const { name, value } = e.target + setCredentials(prev => ({ + ...prev, + [name]: value + })) + } + + return ( +
+
+

💒 כניסה לניהול רשימת מוזמנים

+

הזן שם משתמש וסיסמה לגישה

+ +
+
+ + +
+ +
+ + +
+ + {error &&
{error}
} + + +
+
+
+ ) +} + +export default Login diff --git a/frontend/src/components/SearchFilter.jsx b/frontend/src/components/SearchFilter.jsx index 4ba66cc..89be8be 100644 --- a/frontend/src/components/SearchFilter.jsx +++ b/frontend/src/components/SearchFilter.jsx @@ -47,11 +47,11 @@ function SearchFilter({ onSearch }) { const handleUndoImport = async () => { if (!filters.owner) { - alert('Please select an owner to undo their import') + alert('אנא בחר מייבא כדי לבטל את הייבוא שלו') return } - if (window.confirm(`Are you sure you want to delete all guests imported by ${filters.owner}?`)) { + if (window.confirm(`האם אתה בטוח שברצונך למחוק את כל האורחים שיובאו על ידי ${filters.owner}?`)) { try { const result = await undoImport(filters.owner) alert(result.message) @@ -60,7 +60,7 @@ function SearchFilter({ onSearch }) { onSearch({}) } catch (error) { console.error('Error undoing import:', error) - alert('Failed to undo import') + alert('נכשל בביטול הייבוא') } } } @@ -74,7 +74,7 @@ function SearchFilter({ onSearch }) { name="query" value={filters.query} onChange={handleChange} - placeholder="Search by name, email, or phone..." + placeholder="חיפוש לפי שם, אימייל או טלפון..." />
@@ -83,10 +83,10 @@ function SearchFilter({ onSearch }) { value={filters.rsvpStatus} onChange={handleChange} > - - - - + + + +