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 (
);
}
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 (
);
}
// Provide Firebase instances and userId to child components via context
const firebaseContextValue = { app, db, auth, userId };
return (
);
};
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 (
);
};
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 (
);
}
if (error) {
return (
);
}
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;