import React, { useState, useEffect, createContext, useContext } from ‘react’; import { initializeApp } from ‘firebase/app’; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from ‘firebase/auth’; import { getFirestore, collection, addDoc, query, onSnapshot, serverTimestamp, doc, updateDoc, getDoc, runTransaction } from ‘firebase/firestore’; // Create a context to provide Firebase services and user info throughout the app const FirebaseContext = createContext(null); const App = () => { const [app, setApp] = useState(null); const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); const [loadingFirebase, setLoadingFirebase] = useState(true); const [firebaseError, setFirebaseError] = useState(null); useEffect(() => { // Firebase initialization and authentication const initializeFirebase = async () => { try { const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’; const firebaseConfig = typeof __firebase_config !== ‘undefined’ ? JSON.parse(__firebase_config) : {}; if (!firebaseConfig.apiKey) { throw new Error(“Firebase config is missing API key. Please check __firebase_config variable.”); } const firebaseApp = initializeApp(firebaseConfig); const firestoreDb = getFirestore(firebaseApp); const firebaseAuth = getAuth(firebaseApp); setApp(firebaseApp); setDb(firestoreDb); setAuth(firebaseAuth); // Listen for auth state changes const unsubscribe = onAuthStateChanged(firebaseAuth, async (user) => { if (user) { setUserId(user.uid); } else { // If no user, try to sign in anonymously or with custom token try { if (typeof __initial_auth_token !== ‘undefined’ && __initial_auth_token) { await signInWithCustomToken(firebaseAuth, __initial_auth_token); } else { await signInAnonymously(firebaseAuth); } } catch (error) { console.error(“Firebase Auth Error:”, error); setFirebaseError(`Authentication failed: ${error.message}`); } } setLoadingFirebase(false); }); return () => unsubscribe(); // Cleanup auth listener } catch (error) { console.error(“Failed to initialize Firebase:”, error); setFirebaseError(`Failed to initialize Firebase: ${error.message}`); setLoadingFirebase(false); } }; initializeFirebase(); }, []); if (loadingFirebase) { return (

Loading Application…

); } if (firebaseError) { return (

Error

{firebaseError}

Please check the console for more details.

); } if (!db || !auth || !userId) { // This state should ideally not be reached if loadingFirebase covers initial setup return (

Initializing app…

); } // Provide Firebase instances and userId to child components via context const firebaseContextValue = { app, db, auth, userId }; return (

The Healing Space

Share your experiences anonymously to help yourself and others.

Your anonymous ID: {userId}

© {new Date().getFullYear()} Anonymous Healing App. All rights reserved.

); }; const ExperienceForm = () => { const { db, userId } = useContext(FirebaseContext); const [experienceText, setExperienceText] = useState(”); const [isSubmitting, setIsSubmitting] = useState(false); const [submitMessage, setSubmitMessage] = useState(”); const [moderationStatus, setModerationStatus] = useState(null); // null, ‘pending’, ‘rejected’, ‘approved’ // Function to call LLM for content moderation const moderateContent = async (text) => { setModerationStatus(‘pending’); setSubmitMessage(‘Reviewing your content for appropriateness…’); let chatHistory = []; chatHistory.push({ role: “user”, parts: [{ text: `Review the following text for appropriateness in a public sharing app. Specifically, check for hate speech, harassment, violence, self-harm, sexually explicit content, or illegal activities. Respond with “ACCEPT” if appropriate, and “REJECT” followed by a brief reason if inappropriate. \n\nText: “${text}”` }] }); const payload = { contents: chatHistory }; const apiKey = “”; // Canvas will provide this at runtime const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; try { const response = await fetch(apiUrl, { method: ‘POST’, headers: { ‘Content-Type’: ‘application/json’ }, body: JSON.stringify(payload) }); const result = await response.json(); if (result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts && result.candidates[0].content.parts.length > 0) { const moderationResult = result.candidates[0].content.parts[0].text.trim().toUpperCase(); console.log(“Moderation Result:”, moderationResult); // For debugging if (moderationResult.startsWith(“ACCEPT”)) { setModerationStatus(‘approved’); return { status: ‘approved’ }; } else if (moderationResult.startsWith(“REJECT”)) { const reason = moderationResult.substring(“REJECT”.length).trim(); setModerationStatus(‘rejected’); return { status: ‘rejected’, reason: reason || ‘Content deemed inappropriate.’ }; } else { // If the model gives an unexpected response, default to rejection for safety setModerationStatus(‘rejected’); return { status: ‘rejected’, reason: ‘Unexpected moderation response. Content rejected.’ }; } } else { setModerationStatus(‘rejected’); return { status: ‘rejected’, reason: ‘Could not get moderation response. Please try again.’ }; } } catch (apiError) { console.error(“Error during content moderation API call:”, apiError); setModerationStatus(‘rejected’); return { status: ‘rejected’, reason: `Moderation service error: ${apiError.message}. Please try again.` }; } }; const handleSubmit = async (e) => { e.preventDefault(); if (!experienceText.trim()) { setSubmitMessage(‘Please write something before sharing.’); return; } if (!db || !userId) { setSubmitMessage(‘App not fully initialized. Please wait or refresh.’); return; } setIsSubmitting(true); setSubmitMessage(”); setModerationStatus(null); // Step 1: Content Moderation const moderationResponse = await moderateContent(experienceText); if (moderationResponse.status === ‘rejected’) { setSubmitMessage(`Content rejected: ${moderationResponse.reason}`); setIsSubmitting(false); return; } // Step 2: Submit to Firestore if approved try { // Construct the Firestore collection path using __app_id const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’; const experiencesCollectionRef = collection(db, `artifacts/${appId}/public/data/experiences`); await addDoc(experiencesCollectionRef, { text: experienceText, timestamp: serverTimestamp(), userId: userId, // Storing userId internally for potential future features (e.g., deleting own posts) supports: 0, // Initialize supports count }); setExperienceText(”); setSubmitMessage(‘Thank you for sharing. Your experience has been posted anonymously.’); setModerationStatus(‘approved’); // Ensure status is explicitly set after success setTimeout(() => setSubmitMessage(”), 3000); // Clear message after 3 seconds } catch (error) { console.error(“Error adding document: “, error); setSubmitMessage(`Error sharing experience: ${error.message}. Please try again.`); setModerationStatus(‘error’); // Indicate an error during submission after moderation } finally { setIsSubmitting(false); } }; return (

Share Your Experience

{submitMessage && (

{submitMessage}

)}
); }; const ExperienceList = () => { const { db, userId } = useContext(FirebaseContext); const [experiences, setExperiences] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchTerm, setSearchTerm] = useState(”); const [showReportModal, setShowReportModal] = useState(false); const [reportingExperienceId, setReportingExperienceId] = useState(null); const [reportMessage, setReportMessage] = useState(”); const [isReporting, setIsReporting] = useState(false); useEffect(() => { if (!db) return; try { const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’; const experiencesCollectionRef = collection(db, `artifacts/${appId}/public/data/experiences`); const unsubscribe = onSnapshot(experiencesCollectionRef, (snapshot) => { const fetchedExperiences = snapshot.docs.map(doc => ({ id: doc.id, …doc.data(), // Ensure supports field exists and defaults to 0 if not present supports: doc.data().supports || 0 })); // Sort experiences by timestamp in descending order (most recent first) fetchedExperiences.sort((a, b) => (b.timestamp?.toMillis() || 0) – (a.timestamp?.toMillis() || 0)); setExperiences(fetchedExperiences); setLoading(false); }, (err) => { console.error(“Error fetching experiences: “, err); setError(`Failed to load experiences: ${err.message}`); setLoading(false); }); return () => unsubscribe(); // Cleanup listener on component unmount } catch (err) { console.error(“Error setting up snapshot listener: “, err); setError(`Error setting up listener: ${err.message}`); setLoading(false); } }, [db]); // Re-run effect if db instance changes // Filter experiences based on search term const filteredExperiences = experiences.filter(exp => exp.text.toLowerCase().includes(searchTerm.toLowerCase()) ); const handleSupport = async (experienceId) => { if (!db || !userId) { setReportMessage(‘App not fully initialized. Cannot support.’); setTimeout(() => setReportMessage(”), 3000); return; } const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’; const experienceRef = doc(db, `artifacts/${appId}/public/data/experiences`, experienceId); const userSupportsRef = doc(db, `artifacts/${appId}/users/${userId}/supportedExperiences`, experienceId); try { await runTransaction(db, async (transaction) => { const experienceDoc = await transaction.get(experienceRef); const userSupportsDoc = await transaction.get(userSupportsRef); if (!experienceDoc.exists()) { throw “Experience does not exist!”; } if (userSupportsDoc.exists()) { setReportMessage(“You have already supported this experience.”); setTimeout(() => setReportMessage(”), 3000); return; // Already supported by this user } const newSupports = (experienceDoc.data().supports || 0) + 1; transaction.update(experienceRef, { supports: newSupports }); transaction.set(userSupportsRef, { supportedAt: serverTimestamp() }); // Mark as supported by this user }); // Optionally provide positive feedback to user setReportMessage(“Support added!”); setTimeout(() => setReportMessage(”), 3000); } catch (e) { console.error(“Error supporting experience: “, e); // Only show error if it’s not the “already supported” message if (e !== “You have already supported this experience.”) { setReportMessage(`Error supporting: ${e.message || e}`); setTimeout(() => setReportMessage(”), 3000); } } }; const openReportModal = (id) => { setReportingExperienceId(id); setShowReportModal(true); setReportMessage(”); // Clear previous messages setIsReporting(false); }; const closeReportModal = () => { setShowReportModal(false); setReportingExperienceId(null); setReportMessage(”); setIsReporting(false); }; const submitReport = async () => { if (!db || !userId || !reportingExperienceId) { setReportMessage(‘Error: App not ready or no experience selected.’); return; } setIsReporting(true); setReportMessage(‘Submitting report…’); try { const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘default-app-id’; const reportsCollectionRef = collection(db, `artifacts/${appId}/public/data/reports`); await addDoc(reportsCollectionRef, { experienceId: reportingExperienceId, reportingUserId: userId, timestamp: serverTimestamp(), // Potentially add a reason field if you implement a text input for it status: ‘pending_review’ }); setReportMessage(‘Experience reported successfully. Thank you for your feedback.’); setTimeout(() => closeReportModal(), 2000); } catch (error) { console.error(“Error submitting report: “, error); setReportMessage(`Error submitting report: ${error.message}`); setIsReporting(false); } }; if (loading) { return (

Loading experiences…

); } if (error) { return (

Error: {error}

); } return (

Shared Experiences

setSearchTerm(e.target.value)} />
{reportMessage && (

{reportMessage}

)} {filteredExperiences.length === 0 ? (

No experiences found matching your search.

) : (
{filteredExperiences.map((experience) => (

{experience.text}

{experience.timestamp ? new Date(experience.timestamp.toDate()).toLocaleString() : ‘Just now’}
))}
)} {/* Report Modal */} {showReportModal && (

Report Experience

Are you sure you want to report this experience? Your report will be sent anonymously for review.

{reportMessage && (

{reportMessage}

)}
)}
); }; export default App;