import React, { useState, useEffect } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, onSnapshot, doc, setDoc, deleteDoc, writeBatch } from 'firebase/firestore'; // Initialize Firebase with global variables provided by the environment const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; // Initialize Firebase app and services const app = initializeApp(firebaseConfig); const db = getFirestore(app); const auth = getAuth(app); // --- Icon Components --- const DeleteIcon = () => ( ); const EditIcon = () => ( ); const FrameDownloadIcon = () => ( ); const UploadIcon = () => ( ); const GenerateIcon = () => ( ); const DownloadIcon = () => ( ); const WhatsappIcon = () => ( ); const SaveIcon = () => ( ); const CopyIcon = () => ( ); const SearchIcon = () => ( ); const DashboardIcon = ({ className }) => ( ); const categories = [ 'None', 'All', 'Dentist', 'Children doctor', 'Eye', 'Ent', 'Physiotherapist', 'Skin', 'Orthopadic', 'Cardiologist', 'Neurologist', 'Oncologist', 'Gynecologist', 'Pathologist', 'Lungs', 'Veterinary', 'Radiologist', 'Ayurvedic', 'Other Doctor', 'Car Rental', 'Other', ]; const languages = ['All', 'English', 'Gujarati', 'Hindi']; function App() { // --- State Management --- const [userId, setUserId] = useState(null); const [frames, setFrames] = useState([]); const [dailyImage, setDailyImage] = useState(null); const [generatedPosts, setGeneratedPosts] = useState([]); const [loading, setLoading] = useState(false); const [authReady, setAuthReady] = useState(false); const [error, setError] = useState(null); const [newFrameName, setNewFrameName] = useState(''); const [newFrameFile, setNewFrameFile] = useState(null); const [newFrameNumber, setNewFrameNumber] = useState(''); const [newFrameCategory, setNewFrameCategory] = useState(''); const [newFrameLanguage, setNewFrameLanguage] = useState(''); const [newStartDate, setNewStartDate] = useState(''); const [newEndDate, setNewEndDate] = useState(''); const [showModal, setShowModal] = useState(false); const [modalMessage, setModalMessage] = useState(''); const [modalTitle, setModalTitle] = useState('Error'); const [modalConfig, setModalConfig] = useState(null); // For confirmation modals const [filterCategory, setFilterCategory] = useState('All'); const [filterLanguage, setFilterLanguage] = useState('All'); const [manageFilterCategory, setManageFilterCategory] = useState('None'); const [manageFilterLanguage, setManageFilterLanguage] = useState('All'); const [editModalOpen, setEditModalOpen] = useState(false); const [editingFrame, setEditingFrame] = useState(null); const [editingFrameFile, setEditingFrameFile] = useState(null); const [currentPage, setCurrentPage] = useState('home'); const [dashboardStats, setDashboardStats] = useState({ total: 0, active: 0, expiring: 0, expired: 0 }); const [searchTerm, setSearchTerm] = useState(''); const [selectedFrames, setSelectedFrames] = useState([]); const [activeDashboardFilter, setActiveDashboardFilter] = useState('all'); // 'all', 'active', 'expiring', 'expired' // --- Firestore Path --- const framesCollectionPath = userId ? `artifacts/${appId}/users/${userId}/frames` : null; // --- Utility Functions --- const showCustomModal = (message, title = 'Error', onConfirm = null) => { if (onConfirm) { setModalConfig({ message, title, onConfirm }); } else { setModalMessage(message); setModalTitle(title); setShowModal(true); } }; // --- Firestore Functions --- const saveFramesToFirestore = async (newFrame) => { if (!userId) { setError("User not authenticated. Cannot save frames."); return; } const docRef = doc(collection(db, framesCollectionPath)); try { await setDoc(docRef, { frameName: newFrame.name, frameUrl: newFrame.url, whatsappNumber: newFrame.whatsappNumber, category: newFrame.category, language: newFrame.language, startDate: newFrame.startDate, endDate: newFrame.endDate, createdAt: new Date().toISOString(), }); setNewFrameName(''); setNewFrameFile(null); setNewFrameNumber(''); setNewFrameCategory(''); setNewFrameLanguage(''); setNewStartDate(''); setNewEndDate(''); } catch (e) { console.error("Error adding document: ", e); setError("Failed to save frame. Please try again."); } }; const handleUpdateFrame = async () => { if (!editingFrame) return; const processUpdate = async (frameData) => { const docRef = doc(db, framesCollectionPath, editingFrame.id); try { await setDoc(docRef, frameData, { merge: true }); setEditModalOpen(false); setEditingFrame(null); setEditingFrameFile(null); } catch (e) { console.error("Error updating document: ", e); setError("Failed to update frame. Please try again."); } }; if (editingFrameFile) { const reader = new FileReader(); reader.onload = async (event) => { const updatedFrameData = { ...editingFrame, frameUrl: event.target.result, }; await processUpdate(updatedFrameData); }; reader.readAsDataURL(editingFrameFile); } else { await processUpdate(editingFrame); } }; const deleteFramesFromFirestore = async (frameIds) => { if (!userId || frameIds.length === 0) return; const batch = writeBatch(db); frameIds.forEach(id => { const frameDocRef = doc(db, framesCollectionPath, id); batch.delete(frameDocRef); }); try { await batch.commit(); setSelectedFrames([]); // Clear selection after deletion } catch (e) { console.error("Error deleting documents: ", e); setError("Failed to delete selected frames. Please try again."); } }; // --- Authentication and Data Fetching --- useEffect(() => { const signIn = async () => { try { if (typeof __initial_auth_token !== 'undefined') { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (error) { console.error("Firebase authentication failed:", error); setError("Authentication failed. Please refresh the page."); } }; signIn(); const unsubscribe = onAuthStateChanged(auth, (user) => { setUserId(user ? user.uid : null); setAuthReady(true); }); return () => unsubscribe(); }, []); useEffect(() => { if (!authReady || !userId) return; const framesCol = collection(db, framesCollectionPath); const unsubscribe = onSnapshot(framesCol, (snapshot) => { const fetchedFrames = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), })); setFrames(fetchedFrames); }, (e) => { console.error("Error setting up Firestore listener:", e); setError("Failed to load frames from the database."); }); return () => unsubscribe(); }, [authReady, userId, framesCollectionPath]); // --- Dashboard Stats Calculation --- useEffect(() => { const today = new Date(); today.setHours(0, 0, 0, 0); // Normalize today's date const thirtyDaysFromNow = new Date(); thirtyDaysFromNow.setDate(today.getDate() + 30); const stats = frames.reduce((acc, frame) => { acc.total += 1; const endDate = frame.endDate ? new Date(frame.endDate) : null; if (endDate) { endDate.setHours(0, 0, 0, 0); // Normalize end date if (endDate < today) { acc.expired += 1; } else { acc.active += 1; if (endDate <= thirtyDaysFromNow) { acc.expiring += 1; } } } else { // If no end date, consider it active but not expiring acc.active += 1; } return acc; }, { total: 0, active: 0, expiring: 0, expired: 0 }); setDashboardStats(stats); }, [frames]); // --- Event Handlers --- const handleFrameFileUpload = (e) => { const file = e.target.files[0]; if (file) setNewFrameFile(file); }; const handleAddNewFrame = () => { if (!newFrameFile || !newFrameName || !newFrameNumber || !newFrameCategory || !newFrameLanguage || !newStartDate || !newEndDate) { setError("Please fill in all frame details including start and end dates."); return; } const reader = new FileReader(); reader.onload = (event) => { saveFramesToFirestore({ name: newFrameName, url: event.target.result, whatsappNumber: newFrameNumber, category: newFrameCategory, language: newFrameLanguage, startDate: newStartDate, endDate: newEndDate, }); }; reader.readAsDataURL(newFrameFile); }; const handleDailyImageUpload = (e) => { const file = e.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = (event) => setDailyImage(event.target.result); reader.readAsDataURL(file); } }; const generatePosts = async () => { const filteredFrames = frames.filter(frame => { const isCategoryMatch = filterCategory === 'All' || frame.category === filterCategory; const isLanguageMatch = filterLanguage === 'All' || frame.language === filterLanguage; return isCategoryMatch && isLanguageMatch; }); if (!dailyImage || filteredFrames.length === 0) { setError("Please upload a daily image and select at least one client's frame using the filters."); return; } setLoading(true); setGeneratedPosts([]); setError(null); const posts = []; const dailyImg = new Image(); dailyImg.src = dailyImage; await new Promise(resolve => { dailyImg.onload = resolve; }); for (const frame of filteredFrames) { const frameImg = new Image(); frameImg.src = frame.frameUrl; await new Promise(resolve => { frameImg.onload = resolve; }); const canvas = document.createElement('canvas'); canvas.width = frameImg.width; canvas.height = frameImg.height; const ctx = canvas.getContext('2d'); ctx.drawImage(dailyImg, 0, 0, canvas.width, canvas.height); ctx.drawImage(frameImg, 0, 0, canvas.width, canvas.height); posts.push({ id: frame.id, name: frame.frameName, whatsappNumber: frame.whatsappNumber, url: canvas.toDataURL('image/png'), }); } setGeneratedPosts(posts); setLoading(false); }; const downloadPost = (post) => { const link = document.createElement('a'); link.href = post.url; link.download = `post_${post.whatsappNumber}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const downloadOriginalFrame = (frame) => { const link = document.createElement('a'); link.href = frame.frameUrl; link.download = `original_frame_${frame.frameName.replace(/[^a-z0-9]/gi, '_').toLowerCase()}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const downloadAllAsPngs = async () => { if (generatedPosts.length === 0) { setError("No posts to download."); return; } setLoading(true); for (const post of generatedPosts) { downloadPost(post); await new Promise(resolve => setTimeout(resolve, 300)); } setLoading(false); }; const sendToWhatsapp = (post) => { if (!post.whatsappNumber) { showCustomModal("No WhatsApp number provided for this client's frame. Please add a number before sending."); return; } const message = encodeURIComponent(`Hello ${post.name}, here is your daily social media post!`); const whatsappUrl = `https://wa.me/${post.whatsappNumber}?text=${message}`; window.open(whatsappUrl, '_blank'); }; const handleStartDateChange = (dateString, dateSetter, endDateSetter) => { dateSetter(dateString); if (dateString) { const startDate = new Date(dateString); const endDate = new Date(startDate); endDate.setDate(startDate.getDate() + 365); const year = endDate.getFullYear(); const month = String(endDate.getMonth() + 1).padStart(2, '0'); const day = String(endDate.getDate()).padStart(2, '0'); const formattedEndDate = `${year}-${month}-${day}`; endDateSetter(formattedEndDate); } else { endDateSetter(''); } }; const copyToClipboard = (textToCopy) => { const textArea = document.createElement('textarea'); textArea.value = textToCopy; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); showCustomModal(`${textToCopy} copied to clipboard!`, 'Success'); } catch (err) { console.error('Failed to copy text: ', err); setError('Failed to copy number. Please copy it manually.'); } document.body.removeChild(textArea); }; const handleDashboardFilterClick = (filter) => { setActiveDashboardFilter(filter); setCurrentPage('manage'); // Reset other filters for clarity setManageFilterCategory('All'); setManageFilterLanguage('All'); setSearchTerm(''); }; // --- Components --- const Dashboard = ({ stats, onFilterClick }) => (

Dashboard

); const HomePage = () => ( <>

Upload Daily Image & Generate Posts

{dailyImage && (

Daily Image Preview:

Daily Post
)}

Generated Posts

{generatedPosts.length > 0 ? ( <>
{generatedPosts.map((post) => (
{`Generated
{post.name}
{post.whatsappNumber}
))}
) : (

Generated posts will appear here after you click 'Generate Posts'.

)}
); const ManageFramesPage = () => { const getStatus = (endDate) => { if (!endDate) return { className: 'bg-gray-500', title: 'No End Date' }; const today = new Date(); const end = new Date(endDate); const thirtyDaysFromNow = new Date(); thirtyDaysFromNow.setDate(today.getDate() + 30); today.setHours(0, 0, 0, 0); end.setHours(0, 0, 0, 0); if (end < today) return { className: 'bg-red-500', title: 'Expired' }; if (end <= thirtyDaysFromNow) return { className: 'bg-yellow-500', title: 'Expiring Soon' }; return { className: 'bg-green-500', title: 'Active' }; }; const filteredByDashboard = frames.filter(frame => { if (activeDashboardFilter === 'all') return true; const today = new Date(); const end = frame.endDate ? new Date(frame.endDate) : null; if (!end) { // Treat frames without an end date as 'active' only. They can't be expired or expiring. return activeDashboardFilter === 'active'; } const thirtyDaysFromNow = new Date(); thirtyDaysFromNow.setDate(today.getDate() + 30); today.setHours(0, 0, 0, 0); end.setHours(0, 0, 0, 0); const isExpired = end < today; const isActive = end >= today; const isExpiring = isActive && end <= thirtyDaysFromNow; if (activeDashboardFilter === 'expired') return isExpired; if (activeDashboardFilter === 'expiring') return isExpiring; if (activeDashboardFilter === 'active') return isActive; return true; }); const managedFrames = filteredByDashboard.filter(frame => { if (manageFilterCategory === 'None') return false; const categoryMatch = manageFilterCategory === 'All' || frame.category === manageFilterCategory; const languageMatch = manageFilterLanguage === 'All' || frame.language === manageFilterLanguage; const searchMatch = !searchTerm || frame.frameName.toLowerCase().includes(searchTerm.toLowerCase()) || frame.whatsappNumber.includes(searchTerm); return categoryMatch && languageMatch && searchMatch; }); const handleSelectFrame = (frameId) => { setSelectedFrames(prev => prev.includes(frameId) ? prev.filter(id => id !== frameId) : [...prev, frameId] ); }; const handleSelectAll = (e) => { if (e.target.checked) { setSelectedFrames(managedFrames.map(f => f.id)); } else { setSelectedFrames([]); } }; const handleDeleteSelected = () => { showCustomModal( `Are you sure you want to delete ${selectedFrames.length} selected frame(s)? This action cannot be undone.`, 'Confirm Deletion', () => deleteFramesFromFirestore(selectedFrames) ); }; return (

Manage Client Frames

{/* Add Frame Form */} setNewFrameName(e.target.value)} className="w-full px-4 py-3 bg-gray-700 text-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"/> setNewFrameNumber(e.target.value)} className="w-full px-4 py-3 bg-gray-700 text-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"/>

Your Saved Frames ({managedFrames.length} found)

{activeDashboardFilter !== 'all' && (
Filtering by: {activeDashboardFilter} Clients
)}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-3 bg-gray-700 text-gray-200 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500"/>
{selectedFrames.length > 0 && (
{selectedFrames.length} frame(s) selected.
)}
{managedFrames.length > 0 && (
0} className="h-4 w-4 rounded text-blue-600 bg-gray-700 border-gray-600 focus:ring-blue-500" />Select All
)} {managedFrames.map((frame) => { const status = getStatus(frame.endDate); return (
handleSelectFrame(frame.id)} className="h-4 w-4 rounded text-blue-600 bg-gray-700 border-gray-600 focus:ring-blue-500"/>

{frame.frameName}

{(frame.startDate || frame.endDate) && (

{frame.startDate} to {frame.endDate}

)}
); })}
); } return (
{/* --- Modals --- */} {showModal && (

{modalTitle}

{modalMessage}

)} {modalConfig && (

{modalConfig.title}

{modalConfig.message}

)} {editModalOpen && editingFrame && (

Edit Frame

setEditingFrame({ ...editingFrame, frameName: e.target.value })} className="w-full px-4 py-3 bg-gray-700 text-gray-200 rounded-xl"/> setEditingFrame({ ...editingFrame, whatsappNumber: e.target.value })} className="w-full px-4 py-3 bg-gray-700 text-gray-200 rounded-xl"/>
setEditingFrameFile(e.target.files[0])}/>
)}

Unified Social Media Post & WhatsApp Sender

Upload frames and daily images, then instantly generate and send personalized posts.

User ID: {userId || 'Loading...'}
{/* Navigation */} {/* Page Content */}
{error && (
{error}
)} {currentPage === 'home' && } {currentPage === 'manage' && }
); } export default App;