Business Directory

import React, { useState, useEffect, createContext, useContext } from ‘react’; import { initializeApp } from ‘firebase/app’; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from ‘firebase/auth’; import { getFirestore, collection, query, onSnapshot, addDoc, serverTimestamp, orderBy } from ‘firebase/firestore’; // Context for Firebase services const FirebaseContext = createContext(null); // Firebase Provider component const FirebaseProvider = ({ children }) => { const [firebaseApp, setFirebaseApp] = useState(null); const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); const [isAuthReady, setIsAuthReady] = useState(false); useEffect(() => { try { const firebaseConfig = typeof __firebase_config !== ‘undefined’ ? JSON.parse(__firebase_config) : {}; const app = initializeApp(firebaseConfig); const firestoreDb = getFirestore(app); const firebaseAuth = getAuth(app); setFirebaseApp(app); setDb(firestoreDb); setAuth(firebaseAuth); console.log(“Firebase initialized.”); // Sign in anonymously or with custom token const signInUser = async () => { const initialAuthToken = typeof __initial_auth_token !== ‘undefined’ ? __initial_auth_token : null; try { if (initialAuthToken) { await signInWithCustomToken(firebaseAuth, initialAuthToken); console.log(“Signed in with custom token.”); } else { await signInAnonymously(firebaseAuth); console.log(“Signed in anonymously.”); } } catch (error) { console.error(“Firebase authentication error during sign-in:”, error); } }; signInUser(); // Set up auth state change listener const unsubscribe = onAuthStateChanged(firebaseAuth, (user) => { if (user) { setUserId(user.uid); console.log(“Authentication state changed: User is logged in with UID:”, user.uid); } else { // If no user, generate a random ID for unauthenticated users setUserId(crypto.randomUUID()); console.log(“Authentication state changed: No user logged in, generated anonymous ID:”, userId); // userId here might be from previous render } setIsAuthReady(true); console.log(“isAuthReady set to true.”); }); return () => unsubscribe(); } catch (error) { console.error(“Failed to initialize Firebase:”, error); } }, []); // Run only once on component mount return ( {children} ); }; // Custom hook to use Firebase services const useFirebase = () => useContext(FirebaseContext); // Main App component const App = () => { const { db, userId, isAuthReady } = useFirebase(); const [businesses, setBusinesses] = useState([]); const [searchTerm, setSearchTerm] = useState(”); const [activeCategory, setActiveCategory] = useState(‘All’); // The category used for filtering (All or a specific subcategory) const [selectedMainCategoryForDisplay, setSelectedMainCategoryForDisplay] = useState(null); // Controls which main category’s subcategories are displayed const [showAddForm, setShowAddForm] = useState(false); const [message, setMessage] = useState(”); const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’; // Define the hierarchical structure of categories const mainCategoryGroups = { ‘Retail & Wholesale’: [‘Supermarkets’, ‘General shops’, ‘Wholesale distributors’, ‘Agrovet stores’], ‘Health & Medical’: [‘Hospitals & clinics’, ‘Pharmacies & chemists’, ‘Dental clinics’, ‘Diagnostic labs’, ‘Herbal & nutrition centers’, ‘Physiotherapy & occupational therapy clinics’], ‘Hospitality & Tourism’: [‘Hotels & lodges’, ‘Restaurants & cafés’, ‘Bars & nightclubs’, ‘Tour operators & travel agencies’], ‘Agriculture & Agribusiness’: [‘Farming (crop/livestock)’, ‘Agro-processing’, ‘Agricultural equipment suppliers’, ‘Exporters of produce’], ‘Education & Training’: [‘Schools (primary, secondary)’, ‘Colleges & universities’, ‘Vocational training centers’, ‘Driving schools’], ‘Transport & Logistics’: [‘Car hire & taxi services’, ‘Freight & courier companies’, ‘Clearing & forwarding agents’, ‘Vehicle import/export businesses’], ‘Construction & Real Estate’: [‘Building contractors’, ‘Hardware stores’, ‘Real estate agencies’, ‘Architects & surveyors’], ‘Finance & Legal’: [‘Banks & SACCO’, ‘Microfinance institutions’, ‘Insurance agencies’, ‘Law firms & legal consultants’, ‘Accountants & auditors’], ‘ICT & Technology’: [‘Cyber cafés’, ‘Software & app developers’, ‘IT support services’, ‘Electronics & phone shops’], ‘Creative & Media’: [‘Photography & Videography’, ‘Graphic design & Branding’, ‘Printing & publishing’, ‘Advertising agencies’], ‘Beauty & Wellness’: [‘Salons & barbershops’, ‘Spas & massage parlors’, ‘Cosmetic shops’, ‘Fitness centers & gyms’], ‘Manufacturing & Industry’: [‘Food & beverage processing’, ‘Furniture making’, ‘Textile & garment production’, ‘EPZ (Export Processing Zone) companies’], ‘Professional Services’: [‘Consultants (business, HR, marketing)’, ‘Employment agencies’, ‘Company secretaries’, ‘City/town planners’], ‘Environment & Conservation’: [‘Wildlife conservation’, ‘Environmental NGOs’, ‘Waste management services’], ‘Government Agencies & Regulatory Bodies’: [‘Transport Authority’, ‘Revenue Authority’, ‘Insurance’, ‘Bureaus Standard’, ‘Ethics and Anticorruption’, ‘Environment’, ‘Communication Authority’, ‘Banking’, ‘Retirement Authority’, ‘Airport Authority’, ‘Ports Authority’, ‘Invest Authority’, ‘Betting Control’, ‘Trade Network’, ‘Kenya Power’, ‘Government Portal’, ‘Business Registration’] }; // Fetch businesses from Firestore useEffect(() => { console.log(“Attempting to fetch businesses. db:”, !!db, “isAuthReady:”, isAuthReady, “userId:”, userId); if (!db || !isAuthReady) { console.log(“Firestore or Auth not ready, skipping fetch.”); return; } // Collection path for public data const collectionPath = `artifacts/${appId}/public/data/businesses`; console.log(“Fetching from collection path:”, collectionPath); const businessesColRef = collection(db, collectionPath); const q = query(businessesColRef); const unsubscribe = onSnapshot(q, (snapshot) => { const fetchedBusinesses = snapshot.docs.map(doc => ({ id: doc.id, …doc.data() })); setBusinesses(fetchedBusinesses); console.log(“Businesses fetched:”, fetchedBusinesses.length, “items.”); }, (error) => { console.error(“Error fetching businesses:”, error); setMessage(`Error fetching business listings: ${error.message}. Please check Firebase rules.`); }); return () => unsubscribe(); }, [db, appId, isAuthReady, userId]); // Add userId to dependencies to react to auth changes // Handle click on main category (to toggle subcategory display) const handleMainCategoryToggle = (mainCatName) => { // If clicking the currently selected main category, collapse it and reset filter to ‘All’ if (selectedMainCategoryForDisplay === mainCatName) { setSelectedMainCategoryForDisplay(null); setActiveCategory(‘All’); // Reset filter to All when collapsing } else { // If clicking a different main category, expand it and reset filter to ‘All’ setSelectedMainCategoryForDisplay(mainCatName); setActiveCategory(‘All’); // Reset filter to All when changing main category display } setSearchTerm(”); // Clear search on any category navigation action }; // Handle click on subcategory (to filter businesses) const handleSubCategoryFilter = (subCatName) => { setActiveCategory(subCatName); // Set the specific subcategory as the filter setSearchTerm(”); // Clear search }; const handleSearchChange = (event) => { setSearchTerm(event.target.value); }; const handleAddBusiness = async (newBusiness) => { if (!db || !userId) { setMessage(“Database not ready or user not authenticated.”); console.warn(“Attempted to add business, but DB or User ID not ready.”); return; } try { console.log(“Attempting to add new business:”, newBusiness); const docRef = await addDoc(collection(db, `artifacts/${appId}/public/data/businesses`), { …newBusiness, userId: userId, // Store the user ID of who added the business createdAt: serverTimestamp() // Add a timestamp }); console.log(“Business added successfully with ID:”, docRef.id); setMessage(‘Business added successfully!’); setShowAddForm(false); // Hide form after submission setTimeout(() => setMessage(”), 3000); // Clear message after 3 seconds } catch (error) { console.error(“Error adding business:”, error); setMessage(`Failed to add business: ${error.message}. Please check your Firebase rules.`); // More specific error } }; // Filter businesses based on search term and activeCategory (which is always a specific subcategory or ‘All’) const filteredBusinesses = businesses .filter(business => { const lowerCaseSearchTerm = searchTerm.toLowerCase(); // If search term is empty, just apply category filter if (lowerCaseSearchTerm === ”) { return activeCategory === ‘All’ || business.category === activeCategory; } // Check if any of the specified fields include the search term (case-insensitive) const matchesSearch = business.name.toLowerCase().includes(lowerCaseSearchTerm) || (business.representative && business.representative.toLowerCase().includes(lowerCaseSearchTerm)) || (business.mobile && business.mobile.toLowerCase().includes(lowerCaseSearchTerm)) || (business.telephone && business.telephone.toLowerCase().includes(lowerCaseSearchTerm)) || business.location.toLowerCase().includes(lowerCaseSearchTerm) || business.category.toLowerCase().includes(lowerCaseSearchTerm); const matchesCategory = activeCategory === ‘All’ || business.category === activeCategory; // activeCategory is always a specific subcategory or ‘All’ return matchesSearch && matchesCategory; }) .sort((a, b) => a.name.localeCompare(b.name)); // Client-side sorting console.log(“Filtered businesses for display:”, filteredBusinesses.length, “items.”); return (
{/* Header */}

Local Business Directory

{/* User ID Display */} {userId && isAuthReady && (

Your User ID: {userId}

)} {/* Message Display for main app */} {message && (
{message}
)} {/* Main Category Navigation */} {/* Subcategory Navigation (conditionally displayed) */} {selectedMainCategoryForDisplay && mainCategoryGroups[selectedMainCategoryForDisplay] && (
Subcategories: {mainCategoryGroups[selectedMainCategoryForDisplay].map(subCatName => ( ))}
)} {/* Add Business Button */}
{/* Add Business Form */} {showAddForm && ( setShowAddForm(false)} /> )} {/* Business Listings */} {/* Footer */}

© {new Date().getFullYear()} Local Business Directory. All rights reserved.

); }; // SearchBar Component (Wrapped with React.memo) const SearchBar = React.memo(({ searchTerm, onSearchChange }) => { return (
); }); // BusinessList Component (No changes needed) const BusinessList = ({ businesses }) => { if (businesses.length === 0) { return (
No businesses found matching your criteria. Try adjusting your search or category.
); } return (
{businesses.map(business => ( ))}
); }; // BusinessCard Component (No changes needed) const BusinessCard = ({ business }) => { return (
{business.category}

{business.name}

{business.representative && (

Representative: {business.representative}

)} {business.mobile && (

Mobile: {business.mobile}

)} {business.telephone && (

Telephone: {business.telephone}

)} {business.email && (

Email: {business.email}

)}

Location: {business.location}

Added by: {business.userId ? `${business.userId.substring(0, 8)}…` : ‘N/A’}
); }; // AddBusinessForm Component (Updated for hierarchical category selection and wrapped with React.memo) const AddBusinessForm = React.memo(({ onAddBusiness, mainCategoryGroups, onCancel }) => { const [name, setName] = useState(”); const [representative, setRepresentative] = useState(”); const [mobile, setMobile] = useState(”); const [telephone, setTelephone] = useState(”); const [email, setEmail] = useState(”); const [location, setLocation] = useState(”); const [selectedMainCategoryInForm, setSelectedMainCategoryInForm] = useState(”); // State for main category dropdown const [selectedSubCategoryInForm, setSelectedSubCategoryInForm] = useState(”); // State for subcategory dropdown const [formMessage, setFormMessage] = useState(”); // New state for form-specific messages // Initialize main category dropdown with the first available main category useEffect(() => { const mainCatKeys = Object.keys(mainCategoryGroups); if (mainCatKeys.length > 0) { setSelectedMainCategoryInForm(mainCatKeys[0]); } }, [mainCategoryGroups]); // Update subcategory dropdown when main category selection changes useEffect(() => { if (selectedMainCategoryInForm && mainCategoryGroups[selectedMainCategoryInForm] && mainCategoryGroups[selectedMainCategoryInForm].length > 0) { setSelectedSubCategoryInForm(mainCategoryGroups[selectedMainCategoryInForm][0]); } else { setSelectedSubCategoryInForm(”); } }, [selectedMainCategoryInForm, mainCategoryGroups]); const handleSubmit = (e) => { e.preventDefault(); setFormMessage(”); // Clear previous form messages // Validate that essential fields are filled, including the selected subcategory if (!name || !location || !selectedSubCategoryInForm) { setFormMessage(“Business Name, Location, and Subcategory are required!”); return; } onAddBusiness({ name, representative, mobile, telephone, email, location, category: selectedSubCategoryInForm // Save the selected subcategory as the business’s category }); // Clear form fields after submission setName(”); setRepresentative(”); setMobile(”); setTelephone(”); setEmail(”); setLocation(”); // Reset category dropdowns to initial state setSelectedMainCategoryInForm(Object.keys(mainCategoryGroups)[0] || ”); setSelectedSubCategoryInForm(”); }; // Get the list of subcategories for the currently selected main category in the form const currentSubcategories = selectedMainCategoryInForm ? mainCategoryGroups[selectedMainCategoryInForm] : []; return (

Add New Business Listing

{/* Form-specific message display */} {formMessage && (
{formMessage}
)}
{/* Business Name */}
setName(e.target.value)} className=”w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-400 focus:border-transparent transition-all duration-200″ required />
{/* Representative Name */}
setRepresentative(e.target.value)} className=”w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-400 focus:border-transparent transition-all duration-200″ />
{/* Mobile Number */}
setMobile(e.target.value)} className=”w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-400 focus:border-transparent transition-all duration-200″ />
{/* Telephone Number */}
setTelephone(e.target.value)} className=”w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-400 focus:border-transparent transition-all duration-200″ />
{/* Email Address */}
setEmail(e.target.value)} className=”w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-400 focus:border-transparent transition-all duration-200″ />
{/* Location */}
setLocation(e.target.value)} className=”w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-yellow-400 focus:border-transparent transition-all duration-200″ required />
{/* Category Dropdowns */}
{/* Form Buttons */}
); }); // Wrap the App component with FirebaseProvider export default function ProvidedApp() { return ( ); }