search bar for plants

 import React, { useState, useEffect, createContext, useContext } from 'react';

import { initializeApp } from 'firebase/app';

import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from 'firebase/auth';

import { getFirestore, collection, addDoc, onSnapshot, query, orderBy } from 'firebase/firestore';


// --- Firebase Context and Provider ---

const FirebaseContext = createContext(null);


const FirebaseProvider = ({ children }) => {

    const [db, setDb] = useState(null);

    const [auth, setAuth] = useState(null);

    const [userId, setUserId] = useState(null);

    const [firebaseReady, setFirebaseReady] = useState(false);

    const [error, setError] = useState(null);


    useEffect(() => {

        const initializeFirebase = async () => {

            try {

                // Mandatory global variables provided by the Canvas environment

                const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';

                const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};

                const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;


                if (!Object.keys(firebaseConfig).length) {

                    throw new Error("Firebase configuration not provided. Cannot initialize Firebase.");

                }


                const app = initializeApp(firebaseConfig);

                const firestore = getFirestore(app);

                const firebaseAuth = getAuth(app);


                setDb(firestore);

                setAuth(firebaseAuth);


                // Authenticate user

                if (initialAuthToken) {

                    await signInWithCustomToken(firebaseAuth, initialAuthToken);

                } else {

                    await signInAnonymously(firebaseAuth);

                }


                // Listen for auth state changes to get the user ID

                onAuthStateChanged(firebaseAuth, (user) => {

                    if (user) {

                        setUserId(user.uid);

                    } else {

                        // Fallback for userId if auth fails or not available, use a random ID

                        setUserId(crypto.randomUUID());

                    }

                    setFirebaseReady(true);

                });


            } catch (err) {

                console.error("Failed to initialize Firebase or authenticate:", err);

                setError("Failed to initialize the application. Please try again later.");

                setFirebaseReady(true); // Still set ready to true so UI can show error

            }

        };


        initializeFirebase();

    }, []); // Run only once on component mount


    if (error) {

        return (

            <div className="flex items-center justify-center min-h-screen bg-red-100 text-red-800">

                <p>Error initializing application: {error}</p>

            </div>

        );

    }


    if (!firebaseReady) {

        return (

            <div className="flex items-center justify-center min-h-screen bg-gray-50">

                <div className="flex flex-col items-center">

                    <svg className="animate-spin h-10 w-10 text-green-600 mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">

                        <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>

                        <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>

                    </svg>

                    <p className="text-lg text-gray-700">Loading application...</p>

                </div>

            </div>

        );

    }


    return (

        <FirebaseContext.Provider value={{ db, auth, userId }}>

            {children}

        </FirebaseContext.Provider>

    );

};


// --- Plant Card Component ---

const PlantCard = ({ plant }) => {

    // Placeholder image if plant.imageUrl is not provided or invalid

    const handleError = (e) => {

        e.target.onerror = null; // Prevents infinite loop if placeholder also fails

        e.target.src = 'https://placehold.co/400x300/a3e635/166534?text=No+Image'; // Green placeholder

    };


    return (

        <div className="bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200 transform hover:scale-105 transition-all duration-300">

            <img

                src={plant.imageUrl || 'https://placehold.co/400x300/a3e635/166534?text=No+Image'}

                alt={plant.name}

                className="w-full h-48 object-cover object-center"

                onError={handleError}

            />

            <div className="p-6">

                <h3 className="text-2xl font-bold text-green-800 mb-2">{plant.name}</h3>

                <p className="text-gray-700 text-sm mb-3 line-clamp-3">{plant.description}</p>

                <div className="mb-4">

                    <p className="text-gray-900 font-semibold mb-1">Uses:</p>

                    <ul className="list-disc list-inside text-gray-700 text-sm">

                        {plant.uses.split(',').map((use, index) => (

                            <li key={index} className="mb-1">{use.trim()}</li>

                        ))}

                    </ul>

                </div>

                <span className="inline-block bg-green-100 text-green-800 text-xs font-medium px-3 py-1 rounded-full">

                    {plant.category}

                </span>

            </div>

        </div>

    );

};


// --- Plant Form Component ---

const PlantForm = ({ onSubmit }) => {

    const [name, setName] = useState('');

    const [description, setDescription] = useState('');

    const [uses, setUses] = useState('');

    const [imageUrl, setImageUrl] = useState('');

    const [category, setCategory] = useState('');

    const [message, setMessage] = useState('');

    const [messageType, setMessageType] = useState(''); // 'success' or 'error'


    const categories = ['Herb', 'Spice', 'Fruit', 'Vegetable', 'Flower', 'Tree', 'Other'];


    const handleSubmit = (e) => {

        e.preventDefault();

        if (!name || !description || !uses || !category) {

            setMessage('Please fill in all required fields.');

            setMessageType('error');

            return;

        }


        const newPlant = {

            name,

            description,

            uses,

            imageUrl,

            category,

            createdAt: new Date() // Timestamp for sorting

        };

        onSubmit(newPlant);

        setName('');

        setDescription('');

        setUses('');

        setImageUrl('');

        setCategory('');

        setMessage('Plant added successfully!');

        setMessageType('success');

        setTimeout(() => setMessage(''), 3000); // Clear message after 3 seconds

    };


    return (

        <div className="bg-white p-8 rounded-2xl shadow-xl border border-gray-200 w-full">

            <h2 className="text-3xl font-bold text-green-700 mb-6 text-center">Add New Plant</h2>

            {message && (

                <div className={`p-3 mb-4 rounded-lg text-sm ${messageType === 'success' ? 'bg-green-100 text-green-700 border border-green-300' : 'bg-red-100 text-red-700 border border-red-300'}`}>

                    {message}

                </div>

            )}

            <form onSubmit={handleSubmit} className="space-y-5">

                <div>

                    <label htmlFor="name" className="block text-md font-medium text-gray-700 mb-2">Plant Name <span className="text-red-500">*</span></label>

                    <input

                        type="text"

                        id="name"

                        value={name}

                        onChange={(e) => setName(e.target.value)}

                        placeholder="e.g., Turmeric"

                        className="w-full p-3 border border-gray-300 rounded-lg focus:ring-green-500 focus:border-green-500 transition-all duration-200"

                        required

                    />

                </div>

                <div>

                    <label htmlFor="description" className="block text-md font-medium text-gray-700 mb-2">Description <span className="text-red-500">*</span></label>

                    <textarea

                        id="description"

                        value={description}

                        onChange={(e) => setDescription(e.target.value)}

                        rows="3"

                        placeholder="Brief description of the plant..."

                        className="w-full p-3 border border-gray-300 rounded-lg focus:ring-green-500 focus:border-green-500 transition-all duration-200 resize-y"

                        required

                    ></textarea>

                </div>

                <div>

                    <label htmlFor="uses" className="block text-md font-medium text-gray-700 mb-2">Uses (comma-separated) <span className="text-red-500">*</span></label>

                    <input

                        type="text"

                        id="uses"

                        value={uses}

                        onChange={(e) => setUses(e.target.value)}

                        placeholder="e.g., Anti-inflammatory, Digestive aid, Skin health"

                        className="w-full p-3 border border-gray-300 rounded-lg focus:ring-green-500 focus:border-green-500 transition-all duration-200"

                        required

                    />

                </div>

                <div>

                    <label htmlFor="imageUrl" className="block text-md font-medium text-gray-700 mb-2">Image URL (Optional)</label>

                    <input

                        type="url"

                        id="imageUrl"

                        value={imageUrl}

                        onChange={(e) => setImageUrl(e.target.value)}

                        placeholder="e.g., https://example.com/turmeric.jpg"

                        className="w-full p-3 border border-gray-300 rounded-lg focus:ring-green-500 focus:border-green-500 transition-all duration-200"

                    />

                </div>

                <div>

                    <label htmlFor="category" className="block text-md font-medium text-gray-700 mb-2">Category <span className="text-red-500">*</span></label>

                    <select

                        id="category"

                        value={category}

                        onChange={(e) => setCategory(e.target.value)}

                        className="w-full p-3 border border-gray-300 rounded-lg bg-white focus:ring-green-500 focus:border-green-500 transition-all duration-200"

                        required

                    >

                        <option value="">Select a category</option>

                        {categories.map((cat) => (

                            <option key={cat} value={cat}>{cat}</option>

                        ))}

                    </select>

                </div>

                <button

                    type="submit"

                    className="w-full bg-green-600 hover:bg-green-700 text-white font-semibold py-3 px-6 rounded-lg shadow-md transition transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-opacity-75"

                >

                    Add Plant

                </button>

            </form>

        </div>

    );

};


// --- Plant List Component ---

const PlantList = () => {

    const { db, userId } = useContext(FirebaseContext);

    const [plants, setPlants] = useState([]);

    const [loadingPlants, setLoadingPlants] = useState(true);

    const [plantsError, setPlantsError] = useState(null);

    const [searchTerm, setSearchTerm] = useState(''); // New state for search term


    useEffect(() => {

        if (!db || !userId) return; // Wait until Firebase is ready


        // Path for public data storage

        // __app_id is a global variable provided by the Canvas environment

        const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';

        const plantsCollectionRef = collection(db, `artifacts/${appId}/public/data/plants`);

        const q = query(plantsCollectionRef, orderBy('createdAt', 'desc')); // Order by creation time


        const unsubscribe = onSnapshot(q,

            (snapshot) => {

                const plantsData = snapshot.docs.map(doc => ({

                    id: doc.id,

                    ...doc.data()

                }));

                setPlants(plantsData);

                setLoadingPlants(false);

            },

            (error) => {

                console.error("Error fetching plants:", error);

                setPlantsError("Failed to load plants. Please try refreshing.");

                setLoadingPlants(false);

            }

        );


        // Cleanup subscription on component unmount

        return () => unsubscribe();

    }, [db, userId]);


    // Filter plants based on search term

    const filteredPlants = plants.filter(plant =>

        plant.name.toLowerCase().includes(searchTerm.toLowerCase()) ||

        plant.description.toLowerCase().includes(searchTerm.toLowerCase()) ||

        plant.uses.toLowerCase().includes(searchTerm.toLowerCase())

    );


    // Group filtered plants by category

    const plantsByCategory = filteredPlants.reduce((acc, plant) => {

        const category = plant.category || 'Uncategorized';

        if (!acc[category]) {

            acc[category] = [];

        }

        acc[category].push(plant);

        return acc;

    }, {});


    return (

        <div className="w-full">

            <h2 className="text-3xl font-bold text-green-700 mb-6 text-center">Available Plants</h2>


            {/* Search Bar */}

            <div className="mb-8 p-4 bg-white rounded-xl shadow-md border border-gray-200">

                <label htmlFor="search-plant" className="sr-only">Search Plants</label>

                <input

                    type="text"

                    id="search-plant"

                    value={searchTerm}

                    onChange={(e) => setSearchTerm(e.target.value)}

                    placeholder="Search plants by name, description, or uses..."

                    className="w-full p-3 border border-gray-300 rounded-lg focus:ring-green-500 focus:border-green-500 transition-all duration-200 text-lg"

                />

            </div>


            {loadingPlants && (

                <div className="flex items-center justify-center p-8">

                    <svg className="animate-spin h-8 w-8 text-green-600 mr-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">

                        <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>

                        <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>

                    </svg>

                    <p className="text-gray-700">Loading plants...</p>

                </div>

            )}

            {plantsError && (

                <div className="p-4 bg-red-100 text-red-700 rounded-lg border border-red-300 mb-6">

                    <p>{plantsError}</p>

                </div>

            )}

            {!loadingPlants && filteredPlants.length === 0 && (

                <p className="text-center text-gray-600 p-8">No matching plants found. Try a different search term or add a new plant!</p>

            )}


            {Object.keys(plantsByCategory).sort().map((category) => (

                <div key={category} className="mb-10">

                    <h3 className="text-2xl font-semibold text-green-800 border-b-2 border-green-300 pb-2 mb-6 capitalize">

                        {category}s

                    </h3>

                    <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">

                        {plantsByCategory[category].map((plant) => (

                            <PlantCard key={plant.id} plant={plant} />

                        ))}

                    </div>

                </div>

            ))}

        </div>

    );

};


// --- Main Application Component ---

const RootApplication = () => {

    const { db, userId } = useContext(FirebaseContext); // Correctly consumes context now

    const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';


    const handleAddPlant = async (newPlant) => {

        if (!db) {

            console.error("Firestore DB not initialized.");

            return;

        }

        try {

            // Store public data under /artifacts/{appId}/public/data/plants

            const docRef = await addDoc(collection(db, `artifacts/${appId}/public/data/plants`), newPlant);

            console.log("Document written with ID: ", docRef.id);

        } catch (e) {

            console.error("Error adding document: ", e);

        }

    };


    return (

        <div className="min-h-screen bg-gradient-to-br from-green-50 to-teal-100 font-inter text-gray-800 flex flex-col">

            <style>

                {`

                @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');

                body { font-family: 'Inter', sans-serif; }

                `}

            </style>


            {/* Header */}

            <header className="bg-gradient-to-r from-green-700 to-green-900 text-white py-6 shadow-lg rounded-b-xl">

                <div className="container mx-auto px-4 text-center">

                    <h1 className="text-4xl font-extrabold mb-2 drop-shadow-md">

                        Ayurvedic Plant Data Manager

                    </h1>

                    <p className="text-lg opacity-90">

                        Collect and categorize information about medicinal plants.

                    </p>

                    {userId && (

                        <p className="text-xs mt-2 opacity-70">Your User ID: {userId}</p>

                    )}

                </div>

            </header>


            {/* Main Content */}

            <main className="container mx-auto px-4 py-10 flex-grow">

                <section className="mb-12">

                    <PlantForm onSubmit={handleAddPlant} />

                </section>


                <hr className="border-t-2 border-green-300 my-10" />


                <section>

                    <PlantList />

                </section>

            </main>


            {/* Footer */}

            <footer className="bg-gray-800 text-white text-center py-6 mt-auto rounded-t-xl">

                <p>&copy; 2025 Ayurvedic Plant Data Manager. All rights reserved.</p>

            </footer>

        </div>

    );

};


// --- App Wrapper (default export) ---

// This component ensures FirebaseProvider wraps the entire application

export default function App() {

    return (

        <FirebaseProvider>

            <RootApplication />

        </FirebaseProvider>

    );

}


Comments

Popular posts from this blog

ch 2 pm

pm unit :1

ch 3 pm