Elementor #1075

import React, { useState, useEffect, createContext, useContext } from 'react';
import { initializeApp } from 'firebase/app';
import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut } from 'firebase/auth';
import { getFirestore, doc, getDoc, setDoc, collection, query, onSnapshot, updateDoc, addDoc, deleteDoc, getDocs } from 'firebase/firestore';
import { ChevronLeft, ChevronRight, Calendar, User, LogIn, PlusCircle, DollarSign, Award, Home, CheckCircle, XCircle, Clock } from 'lucide-react';
// Contexto para Firebase y el usuario
const FirebaseContext = createContext(null);
// Componente para el mensaje de carga/error
const MessageModal = ({ message, type, onClose }) => {
if (!message) return null;
const bgColor = type === 'error' ? 'bg-red-500' : 'bg-green-500';
const icon = type === 'error' ? : ;
return (
{icon}

{message}


);
};
// Componente de la aplicación principal
function App() {
const [db, setDb] = useState(null);
const [auth, setAuth] = useState(null);
const [userId, setUserId] = useState(null);
const [isAuthReady, setIsAuthReady] = useState(false);
const [currentPage, setCurrentPage] = useState('login'); // Controla la navegación
const [message, setMessage] = useState('');
const [messageType, setMessageType] = useState('info');
// Configuración inicial de Firebase y autenticación
useEffect(() => {
try {
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
const firebaseConfig = JSON.parse(typeof __firebase_config !== 'undefined' ? __firebase_config : '{}');
if (Object.keys(firebaseConfig).length === 0) {
console.error("Firebase config is missing. Please ensure __firebase_config is defined.");
setMessage("Error: Configuración de Firebase no encontrada. Contacta al soporte.");
setMessageType("error");
return;
}
const app = initializeApp(firebaseConfig);
const firestoreDb = getFirestore(app);
const firebaseAuth = getAuth(app);
setDb(firestoreDb);
setAuth(firebaseAuth);
// Listener para el estado de autenticación
const unsubscribe = onAuthStateChanged(firebaseAuth, async (user) => {
if (user) {
setUserId(user.uid);
// Si el usuario es anónimo y hay un token inicial, no hacemos nada más.
// Si el usuario es anónimo sin token inicial, o si es un usuario con cuenta,
// aseguramos que su perfil exista en Firestore.
if (user.isAnonymous && typeof __initial_auth_token === 'undefined') {
// Usuario anónimo sin token inicial, no se guarda en Firestore directamente aquí
} else {
// Usuario con cuenta o anónimo con token inicial (que se convierte en usuario con cuenta si se registra)
const userRef = doc(firestoreDb, `artifacts/${appId}/users/${user.uid}/profile`, 'data');
const userSnap = await getDoc(userRef);
if (!userSnap.exists()) {
await setDoc(userRef, {
email: user.email || 'anonymous',
points: 0,
createdAt: new Date(),
userId: user.uid // Guardar el userId para referencia
});
}
}
setCurrentPage('home'); // Redirigir a la página principal si está autenticado
} else {
setUserId(null);
setCurrentPage('login'); // Redirigir al login si no está autenticado
// Intentar iniciar sesión anónimamente si no hay token inicial
if (typeof __initial_auth_token === 'undefined') {
try {
await signInAnonymously(firebaseAuth);
} catch (error) {
console.error("Error signing in anonymously:", error);
setMessage("Error al iniciar sesión anónimamente. " + error.message);
setMessageType("error");
}
}
}
setIsAuthReady(true); // Firebase Auth está listo
});
// Si hay un token de autenticación inicial, úsalo para iniciar sesión
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
signInWithCustomToken(firebaseAuth, __initial_auth_token).catch((error) => {
console.error("Error signing in with custom token:", error);
setMessage("Error al iniciar sesión con token personalizado. " + error.message);
setMessageType("error");
});
} else {
// Si no hay token inicial, el onAuthStateChanged ya manejará el inicio de sesión anónimo.
}
return () => unsubscribe(); // Limpiar el listener al desmontar
} catch (error) {
console.error("Error initializing Firebase:", error);
setMessage("Error al inicializar Firebase. " + error.message);
setMessageType("error");
}
}, []);
const handleSignOut = async () => {
try {
await signOut(auth);
setMessage("Sesión cerrada correctamente.");
setMessageType("success");
setCurrentPage('login');
} catch (error) {
console.error("Error signing out:", error);
setMessage("Error al cerrar sesión: " + error.message);
setMessageType("error");
}
};
const showMessage = (msg, type) => {
setMessage(msg);
setMessageType(type);
};
const closeMessage = () => {
setMessage('');
};
if (!isAuthReady) {
return (

Cargando aplicación...

);
}
return (

CARPA LA 66 - Reservas


{currentPage === 'login' && }
{currentPage === 'register' && }
{currentPage === 'home' && userId && }
{currentPage === 'my-reservations' && userId && }
{currentPage === 'profile' && userId && }
{currentPage === 'payment' && userId && }

© {new Date().getFullYear()} CARPA LA 66. Todos los derechos reservados.

{userId &&

ID de Usuario: {userId}

}



);
}
// Componente de autenticación (Login/Registro)
const AuthPage = ({ setCurrentPage, isRegister = false }) => {
const { auth, showMessage } = useContext(FirebaseContext);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
if (isRegister) {
await createUserWithEmailAndPassword(auth, email, password);
showMessage('Registro exitoso. ¡Bienvenido!', 'success');
} else {
await signInWithEmailAndPassword(auth, email, password);
showMessage('Inicio de sesión exitoso. ¡Bienvenido!', 'success');
}
// La redirección a 'home' se maneja en onAuthStateChanged en App.js
} catch (error) {
console.error("Authentication error:", error);
showMessage(`Error al ${isRegister ? 'registrar' : 'iniciar sesión'}: ${error.message}`, 'error');
} finally {
setLoading(false);
}
};
return (

{isRegister ? 'Crear Cuenta' : 'Iniciar Sesión'}


setEmail(e.target.value)}
required
/>

setPassword(e.target.value)}
required
/>

{isRegister ? (

¿Ya tienes una cuenta?{' '}

) : (

¿No tienes una cuenta?{' '}

)}
);
};
// Componente de la página principal (Home)
const HomePage = () => {
const { db, userId, showMessage, appId } = useContext(FirebaseContext);
const [courts, setCourts] = useState([]);
const [selectedCourt, setSelectedCourt] = useState(null);
const [selectedDate, setSelectedDate] = useState(new Date());
const [selectedTime, setSelectedTime] = useState('');
const [loading, setLoading] = useState(false);
const [availableTimes, setAvailableTimes] = useState([]);
// Horarios disponibles para las reservas (ejemplo)
const allPossibleTimes = [
'08:00', '09:00', '10:00', '11:00', '12:00',
'13:00', '14:00', '15:00', '16:00', '17:00',
'18:00', '19:00', '20:00', '21:00'
];
// Cargar canchas desde Firestore
useEffect(() => {
if (!db) return;
const courtsColRef = collection(db, `artifacts/${appId}/public/data/courts`);
const unsubscribe = onSnapshot(courtsColRef, (snapshot) => {
const fetchedCourts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
setCourts(fetchedCourts);
if (fetchedCourts.length > 0 && !selectedCourt) {
setSelectedCourt(fetchedCourts[0]); // Seleccionar la primera cancha por defecto
}
}, (error) => {
console.error("Error fetching courts:", error);
showMessage("Error al cargar las canchas.", "error");
});
// Añadir canchas de ejemplo si no existen
const checkAndAddCourts = async () => {
const courtsSnapshot = await getDoc(doc(courtsColRef, 'padel1')); // Solo verificar una
if (!courtsSnapshot.exists()) {
await Promise.all([
setDoc(doc(courtsColRef, 'padel1'), { name: 'Cancha de Pádel 1', type: 'pádel', price: 120, description: 'Cancha de pádel de alta calidad.' }),
setDoc(doc(courtsColRef, 'padel2'), { name: 'Cancha de Pádel 2', type: 'pádel', price: 120, description: 'Cancha de pádel de alta calidad.' }),
setDoc(doc(courtsColRef, 'padel3'), { name: 'Cancha de Pádel 3', type: 'pádel', price: 120, description: 'Cancha de pádel de alta calidad.' }),
setDoc(doc(courtsColRef, 'padel4'), { name: 'Cancha de Pádel 4', type: 'pádel', price: 120, description: 'Cancha de pádel de alta calidad.' }),
setDoc(doc(courtsColRef, 'padel5'), { name: 'Cancha de Pádel 5', type: 'pádel', price: 120, description: 'Cancha de pádel de alta calidad.' }),
setDoc(doc(courtsColRef, 'microfutbol1'), { name: 'Cancha de Microfútbol', type: 'microfútbol', price: 100, description: 'Cancha de microfútbol reglamentaria.' }),
setDoc(doc(courtsColRef, 'voleibol1'), { name: 'Cancha de Voleibol', type: 'voleibol', price: 90, description: 'Cancha de voleibol de arena.' }),
]);
console.log("Canchas de ejemplo añadidas.");
}
};
checkAndAddCourts();
return () => unsubscribe();
}, [db, showMessage, appId, selectedCourt]);
// Actualizar horarios disponibles cuando cambie la cancha o la fecha
useEffect(() => {
const fetchAvailableTimes = async () => {
if (!db || !selectedCourt || !selectedDate) {
setAvailableTimes([]);
return;
}
const formattedDate = selectedDate.toISOString().split('T')[0]; //YYYY-MM-DD
const reservationsColRef = collection(db, `artifacts/${appId}/public/data/reservations`);
const q = query(
reservationsColRef,
// No usar `where` para evitar problemas de índices, filtrar en cliente
);
const snapshot = await getDocs(q); // Usar getDocs para una consulta única
const existingReservations = snapshot.docs
.map(doc => doc.data())
.filter(res => res.courtId === selectedCourt.id && res.date === formattedDate);
const reservedHours = existingReservations.map(res => res.time);
const currentHour = new Date().getHours();
const currentMinute = new Date().getMinutes();
const filteredTimes = allPossibleTimes.filter(time => {
const [hour, minute] = time.split(':').map(Number);
const reservationDateTime = new Date(selectedDate);
reservationDateTime.setHours(hour, minute, 0, 0);
// No permitir reservar en el pasado
if (reservationDateTime < new Date()) { return false; } return !reservedHours.includes(time); }); setAvailableTimes(filteredTimes); setSelectedTime(''); // Resetear la hora seleccionada }; fetchAvailableTimes(); }, [selectedCourt, selectedDate, db, appId]); const handleDateChange = (days) => {
const newDate = new Date(selectedDate);
newDate.setDate(newDate.getDate() + days);
setSelectedDate(newDate);
};
const handleReservation = async () => {
if (!selectedCourt || !selectedDate || !selectedTime) {
showMessage('Por favor, selecciona una cancha, fecha y hora.', 'error');
return;
}
setLoading(true);
try {
const formattedDate = selectedDate.toISOString().split('T')[0]; //YYYY-MM-DD
// Verificar si la cancha ya está reservada para esa hora y fecha (doble chequeo)
const reservationsColRef = collection(db, `artifacts/${appId}/public/data/reservations`);
const q = query(
reservationsColRef,
// Filtrar en cliente para evitar problemas de índices
);
const snapshot = await getDocs(q);
const existingReservation = snapshot.docs.find(doc =>
doc.data().courtId === selectedCourt.id &&
doc.data().date === formattedDate &&
doc.data().time === selectedTime
);
if (existingReservation) {
showMessage('Esta cancha ya está reservada para la hora seleccionada. Por favor, elige otra.', 'error');
return;
}
// Crear la reserva
await addDoc(reservationsColRef, {
userId: userId,
courtId: selectedCourt.id,
courtName: selectedCourt.name,
date: formattedDate,
time: selectedTime,
price: selectedCourt.price,
status: 'pending', // Podría ser 'pending' hasta el pago
createdAt: new Date(),
});
// Actualizar puntos del usuario
const userProfileRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data');
const userSnap = await getDoc(userProfileRef);
if (userSnap.exists()) {
const currentPoints = userSnap.data().points || 0;
await updateDoc(userProfileRef, { points: currentPoints + 10 }); // Ejemplo: 10 puntos por reserva
}
showMessage('Reserva realizada con éxito. ¡Gracias!', 'success');
setSelectedTime(''); // Limpiar la selección de hora
} catch (error) {
console.error("Error making reservation:", error);
showMessage('Error al realizar la reserva: ' + error.message, 'error');
} finally {
setLoading(false);
}
};
const today = new Date();
today.setHours(0, 0, 0, 0); // Para comparar solo la fecha
return (

Reservar Cancha

{/* Selector de Canchas */}

{courts.map((court) => (

))}
{selectedCourt && (
<>

Cancha Seleccionada: {selectedCourt.name}

{selectedCourt.description}

Precio: ${selectedCourt.price} / hora

{/* Selector de Fecha */}


{selectedDate.toLocaleDateString('es-ES', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}

{/* Selector de Hora */}

{availableTimes.length > 0 ? (
availableTimes.map((time) => (

))
) : (

No hay horarios disponibles para esta fecha o cancha.

)}
{/* Botón de Reservar */}


)}
);
};
// Componente de Mis Reservas
const MyReservationsPage = () => {
const { db, userId, showMessage, appId } = useContext(FirebaseContext);
const [reservations, setReservations] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!db || !userId) return;
const reservationsColRef = collection(db, `artifacts/${appId}/public/data/reservations`);
const unsubscribe = onSnapshot(reservationsColRef, (snapshot) => {
const userReservations = snapshot.docs
.map(doc => ({ id: doc.id, ...doc.data() }))
.filter(res => res.userId === userId) // Filtrar por userId en el cliente
.sort((a, b) => new Date(a.date + ' ' + a.time) - new Date(b.date + ' ' + b.time)); // Ordenar por fecha y hora
setReservations(userReservations);
setLoading(false);
}, (error) => {
console.error("Error fetching user reservations:", error);
showMessage("Error al cargar tus reservas.", "error");
setLoading(false);
});
return () => unsubscribe();
}, [db, userId, showMessage, appId]);
if (loading) {
return (
);
}
return (

Mis Reservas

{reservations.length === 0 ? (

No tienes reservas activas. ¡Es hora de reservar una cancha!


) : (
{reservations.map((reservation) => (

{reservation.courtName}


Fecha: {new Date(reservation.date).toLocaleDateString('es-ES', { year: 'numeric', month: 'long', day: 'numeric' })}


Hora: {reservation.time}


Precio: ${reservation.price}


Estado: {reservation.status === 'pending' ? 'Pendiente de Pago' : 'Confirmada'}

))}
)}
);
};
// Componente de Perfil de Usuario
const ProfilePage = () => {
const { db, userId, showMessage, appId } = useContext(FirebaseContext);
const [userProfile, setUserProfile] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!db || !userId) return;
const userProfileRef = doc(db, `artifacts/${appId}/users/${userId}/profile`, 'data');
const unsubscribe = onSnapshot(userProfileRef, (docSnap) => {
if (docSnap.exists()) {
setUserProfile(docSnap.data());
} else {
console.log("No user profile data found!");
setUserProfile({ email: 'N/A', points: 0 }); // Default if not found
}
setLoading(false);
}, (error) => {
console.error("Error fetching user profile:", error);
showMessage("Error al cargar tu perfil.", "error");
setLoading(false);
});
return () => unsubscribe();
}, [db, userId, showMessage, appId]);
if (loading) {
return (
);
}
return (

Mi Perfil

{userProfile && (

Correo Electrónico:

{userProfile.email}

Puntos de Fidelidad:

{userProfile.points}

¡Sigue reservando para ganar más promociones!

ID de Usuario:

{userId}

Este ID es único para tu cuenta.

)}
);
};
// Componente de Pasarela de Pagos
const PaymentPage = ({ setCurrentPage }) => {
const { showMessage } = useContext(FirebaseContext);
const [loading, setLoading] = useState(false);
const [paymentAmount, setPaymentAmount] = useState(120); // Ejemplo de monto a pagar
const paypalContainerRef = React.useRef(null);
useEffect(() => {
// Cargar el SDK de PayPal si no está ya cargado
if (window.paypal) {
renderPayPalButtons();
} else {
const script = document.createElement('script');
script.src = `https://www.paypal.com/sdk/js?client-id=YOUR_PAYPAL_CLIENT_ID¤cy=USD`; // Reemplaza YOUR_PAYPAL_CLIENT_ID
script.onload = () => {
console.log("PayPal SDK loaded.");
renderPayPalButtons();
};
script.onerror = () => {
showMessage("Error al cargar el SDK de PayPal. Por favor, inténtalo de nuevo.", "error");
};
document.body.appendChild(script);
}
return () => {
// Limpieza si es necesario
if (paypalContainerRef.current) {
paypalContainerRef.current.innerHTML = ''; // Limpiar botones de PayPal al desmontar
}
};
}, [paymentAmount]); // Dependencia del monto para re-renderizar botones si cambia
const renderPayPalButtons = () => {
if (!window.paypal || !paypalContainerRef.current) return;
// Limpiar el contenedor antes de renderizar para evitar duplicados
paypalContainerRef.current.innerHTML = '';
window.paypal.Buttons({
createOrder: async (data, actions) => {
// En un entorno real, esta llamada iría a tu Firebase Cloud Function
// para crear una orden de PayPal de forma segura en el backend.
// La Cloud Function usaría tus credenciales de PayPal para interactuar con la API de PayPal.
setLoading(true);
try {
// Simulación de llamada a Cloud Function para crear la orden
// const response = await fetch('/api/create-paypal-order', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ amount: paymentAmount, currency: 'USD' })
// });
// const order = await response.json();
// if (order.id) {
// return order.id;
// } else {
// throw new Error('Error al crear la orden de PayPal.');
// }
// Simulación de una orden ID de PayPal
console.log("Simulando creación de orden de PayPal...");
return actions.order.create({
purchase_units: [{
amount: {
value: paymentAmount.toFixed(2),
currency_code: 'USD'
}
}]
});
} catch (error) {
console.error("Error creating PayPal order:", error);
showMessage("Error al iniciar el pago con PayPal: " + error.message, "error");
setLoading(false);
return null; // Indica que la creación de la orden falló
}
},
onApprove: async (data, actions) => {
// En un entorno real, esta llamada iría a tu Firebase Cloud Function
// para capturar la orden de PayPal de forma segura en el backend.
// La Cloud Function verificaría el pago y actualizaría el estado de la reserva en Firestore.
setLoading(true);
try {
// Simulación de llamada a Cloud Function para capturar la orden
// const response = await fetch('/api/capture-paypal-order', {
// method: 'POST',
// headers: { 'Content-Type': 'application/json' },
// body: JSON.stringify({ orderID: data.orderID })
// });
// const capture = await response.json();
// Simulación de aprobación de pago
console.log("Simulando captura de pago de PayPal...");
const capture = await actions.order.capture();
if (capture.status === 'COMPLETED') {
showMessage('Pago procesado con éxito. ¡Tu reserva está confirmada!', 'success');
// Aquí deberías actualizar el estado de la reserva en Firestore a 'confirmed'
// y otorgar los puntos de fidelidad. Esto también debería ser manejado por
// una Cloud Function para mayor seguridad y consistencia.
setCurrentPage('my-reservations');
} else {
throw new Error('El pago no se completó. Estado: ' + capture.status);
}
} catch (error) {
console.error("Error capturing PayPal payment:", error);
showMessage('Error al procesar el pago: ' + error.message, 'error');
} finally {
setLoading(false);
}
},
onCancel: (data) => {
console.log('Pago de PayPal cancelado:', data);
showMessage('Pago cancelado por el usuario.', 'info');
setLoading(false);
},
onError: (err) => {
console.error('Error en PayPal:', err);
showMessage('Ocurrió un error con PayPal. Por favor, inténtalo de nuevo.', 'error');
setLoading(false);
}
}).render(paypalContainerRef.current); // Renderiza los botones en el div
};
return (

Realizar Pago con PayPal

Monto a pagar:

${paymentAmount.toFixed(2)} USD

{loading && (

Procesando pago...

)}
{/* Contenedor donde se renderizarán los botones de PayPal */}
{/* Los botones de PayPal se cargarán aquí */}

Para una integración completa y segura, necesitarás configurar
Firebase Cloud Functions para manejar la creación y captura de órdenes de PayPal
en el backend, utilizando tus credenciales de API de PayPal de forma segura.
Reemplaza 'YOUR_PAYPAL_CLIENT_ID' en el script de PayPal con tu ID de cliente real.


);
};
export default App;

Subir