import React, { useState, useEffect, useRef } from 'react';
import {
LayoutDashboard, Map, Plane, Users, Settings, LogOut,
Plus, Save, FileDown, Trash2, Calendar, DollarSign, Lock,
CheckCircle
} from 'lucide-react';
// --- FIREBASE IMPORTS ---
import { initializeApp } from 'firebase/app';
import {
getAuth,
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged
} from 'firebase/auth';
import {
getFirestore,
collection, addDoc, query, where, onSnapshot,
deleteDoc, doc, setDoc, getDoc, orderBy
} from 'firebase/firestore';
// --- CONFIGURATION ---
// TODO: Replace this object with your OWN Firebase Config from the Firebase Console
const firebaseConfig = {
apiKey: "REPLACE_WITH_YOUR_API_KEY",
authDomain: "REPLACE_WITH_YOUR_PROJECT_ID.firebaseapp.com",
projectId: "REPLACE_WITH_YOUR_PROJECT_ID",
storageBucket: "REPLACE_WITH_YOUR_PROJECT_ID.appspot.com",
messagingSenderId: "REPLACE_WITH_YOUR_SENDER_ID",
appId: "REPLACE_WITH_YOUR_APP_ID"
};
// Initialize Firebase
// We use a try-catch to prevent crashing if config is empty during copy-paste
let app, auth, db;
try {
app = initializeApp(firebaseConfig);
auth = getAuth(app);
db = getFirestore(app);
} catch (e) {
console.error("Firebase not initialized. Check config.");
}
// --- UTILS ---
const loadScript = (src) => {
return new Promise((resolve, reject) => {
if (document.querySelector(`script[src="${src}"]`)) return resolve();
const script = document.createElement('script');
script.src = src;
script.onload = resolve;
script.onerror = reject;
document.body.appendChild(script);
});
};
// --- COMPONENTS ---
// 1. AUTH SCREEN (Login/Register)
const AuthScreen = ({ onLogin }) => {
const [isRegister, setIsRegister] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
try {
if (isRegister) {
await createUserWithEmailAndPassword(auth, email, password);
} else {
await signInWithEmailAndPassword(auth, email, password);
}
} catch (err) {
setError(err.message);
}
};
return (
{error &&
);
};
// 2. SUBSCRIPTION WALL
const SubscriptionWall = ({ user, onLogout }) => {
return (
);
};
// 3. MAIN DASHBOARD (Same functionality as before, adjusted for standard paths)
const Dashboard = ({ itineraries, leads }) => {
const activeTrips = itineraries.filter(i => new Date(i.travelDate) >= new Date()).length;
const totalRevenue = itineraries.reduce((acc, curr) => acc + (parseFloat(curr.finalPrice) || 0), 0);
return (
);
};
// ... [ItineraryBuilder, TicketGenerator, LeadManager, Settings components remain mostly same as previous logic]
// I will include condensed versions here for the full file
const ItineraryBuilder = ({ user, agencyProfile, onSave }) => {
const [formData, setFormData] = useState({
guestName: '', travelDate: '', numDays: 5, numAdults: 2,
pkgCategory: 'Standard', vehicleType: 'Sedan', pickupLoc: 'Srinagar', dropLoc: 'Srinagar',
costHotel: 0, costCab: 0, costActivity: 0, profitMargin: 0,
itinerary: [],
});
const previewRef = useRef();
useEffect(() => {
if (formData.itinerary.length === 0) {
const arr = Array.from({length: formData.numDays}, (_, i) => ({ title: `Day ${i+1}`, desc: '' }));
setFormData(p => ({ ...p, itinerary: arr }));
}
}, [formData.numDays]);
const save = () => {
const finalPrice = (parseFloat(formData.costHotel)||0) + (parseFloat(formData.costCab)||0) + (parseFloat(formData.costActivity)||0) + (parseFloat(formData.profitMargin)||0);
onSave({ ...formData, finalPrice, createdAt: new Date() });
};
const generatePDF = async () => {
await loadScript("https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js");
window.html2pdf().set({ margin: 0, filename: 'Quote.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4' } }).from(previewRef.current).save();
};
return (
);
};
const SettingsPage = ({ profile, onUpdate }) => {
const [local, setLocal] = useState(profile || {});
return (
);
};
// --- MAIN APP SHELL ---
export default function TravelSaaS() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [profile, setProfile] = useState(null);
const [view, setView] = useState('dashboard');
const [data, setData] = useState({ itineraries: [], leads: [] });
// 1. Auth Listener
useEffect(() => {
if(!auth) return;
const unsub = onAuthStateChanged(auth, async (u) => {
setUser(u);
if (u) {
// Fetch Profile
const userRef = doc(db, 'users', u.uid);
const docSnap = await getDoc(userRef);
if (docSnap.exists()) {
setProfile(docSnap.data());
} else {
// New User - Create default profile
const defaultProfile = {
email: u.email,
subscriptionStatus: 'inactive', // Default to inactive
name: 'My Agency',
createdAt: new Date().toISOString()
};
await setDoc(userRef, defaultProfile);
setProfile(defaultProfile);
}
// Setup Listeners for Data
const qItin = query(collection(db, `users/${u.uid}/itineraries`), orderBy('createdAt', 'desc'));
onSnapshot(qItin, s => setData(p => ({ ...p, itineraries: s.docs.map(d => ({id:d.id, ...d.data()})) })));
const qLeads = query(collection(db, `users/${u.uid}/leads`));
onSnapshot(qLeads, s => setData(p => ({ ...p, leads: s.docs.map(d => ({id:d.id, ...d.data()})) })));
}
setLoading(false);
});
return () => unsub();
}, []);
const handleSaveItin = async (itin) => {
await addDoc(collection(db, `users/${user.uid}/itineraries`), itin);
setView('dashboard');
};
const handleUpdateProfile = async (newProfile) => {
await setDoc(doc(db, 'users', user.uid), { ...profile, ...newProfile }, { merge: true });
setProfile({ ...profile, ...newProfile });
};
const handleLogout = () => signOut(auth);
if (loading) return ;
// 3. Logged In BUT No Subscription -> Show Wall
// Note: To test this locally, manually change subscriptionStatus to 'active' in Firebase Console
if (profile?.subscriptionStatus !== 'active') {
return ;
}
// 4. Logged In & Active -> Show App
return (
);
}
TravelPro SaaS
Agent Portal
{error}
}
Subscription Required
Hello {user.email}. Your account is currently Inactive.
How to activate:
- Contact Support to purchase a license.
- Once payment is verified, we will activate your access instantly.
Agency Overview
Active Trips
{activeTrips}
Total Revenue
₹{totalRevenue.toLocaleString()}
Leads
{leads.length}
Builder
setFormData({...formData, guestName: e.target.value})} />
setFormData({...formData, numDays: parseInt(e.target.value)})} />
{formData.itinerary.map((day, i) => (
))}
Day {i+1}
{
const n = [...formData.itinerary]; n[i].title = e.target.value; setFormData({...formData, itinerary: n});
}}/>
setFormData({...formData, costHotel: e.target.value})} />
setFormData({...formData, profitMargin: e.target.value})} />
{agencyProfile.logoUrl &&
}
{agencyProfile.name || "Agency Name"}
{agencyProfile.phone}
Itinerary for {formData.guestName}
{formData.itinerary.map((d, i) => (
))}
Day {i+1}
{d.title}
{d.desc}
Total: ₹ {((parseFloat(formData.costHotel)||0) + (parseFloat(formData.profitMargin)||0)).toLocaleString()}
Settings
setLocal({...local, name:e.target.value})} />
setLocal({...local, logoUrl:e.target.value})} />
setLocal({...local, phone:e.target.value})} />
Loading SaaS...
;
// 2. Not Logged In -> Show Auth Screen
if (!user) return TravelPro
{user.email}
{view === 'dashboard' && }
{view === 'itinerary' && }
{view === 'settings' && }
