Files
prayer-app/src/app/page.tsx
T

283 lines
9.3 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useAuth } from '@/context/AuthContext';
import Login from '@/components/Login';
import LoadingSkeleton from '@/components/LoadingSkeleton';
import BottomNav from '@/components/BottomNav';
import ReminderBanner from '@/components/ReminderBanner';
import DailyEntry from '@/components/DailyEntry';
import CalendarView from '@/components/CalendarView';
import ReflectionsList from '@/components/ReflectionsList';
import ThingsToPrayFor from '@/components/ThingsToPrayFor';
import AboutPage from '@/components/AboutPage';
import HomePage from '@/components/HomePage';
import AnsweredPrayers from '@/components/AnsweredPrayers';
import { db } from '@/lib/firebase';
import { collection, addDoc, getDocs, updateDoc, query, where, doc } from 'firebase/firestore';
import toast from 'react-hot-toast';
interface Prayer {
id: string;
userId: string;
date: string;
text: string;
prayFor?: string;
answered?: boolean;
answerDetails?: string;
prayFrequency?: number; // 1-7 times per week
lastPrayedFor?: string; // ISO date string
}
export default function Home() {
const [activeTab, setActiveTab] = useState<'home' | 'compose' | 'journal' | 'answered' | 'praying' | 'reflections' | 'about'>('home');
const [prayers, setPrayers] = useState<Prayer[]>([]);
const [editingPrayer, setEditingPrayer] = useState<Prayer | null>(null);
// Form state
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
const [text, setText] = useState('');
const [prayFor, setPrayFor] = useState('');
const [answered, setAnswered] = useState(false);
const [answerDetails, setAnswerDetails] = useState('');
const { user, loading: authLoading } = useAuth();
// Load prayers from Firestore
useEffect(() => {
if (!user) return;
const loadPrayers = async () => {
try {
const prayersRef = collection(db, 'prayers');
const q = query(prayersRef, where('userId', '==', user.uid));
const querySnapshot = await getDocs(q);
const loadedPrayers: Prayer[] = [];
querySnapshot.forEach((doc) => {
loadedPrayers.push({ id: doc.id, ...doc.data() } as Prayer);
});
setPrayers(loadedPrayers);
} catch (error) {
console.error('Error loading prayers:', error);
toast.error('Failed to load prayers');
}
};
loadPrayers();
}, [user]);
// Load draft from localStorage
useEffect(() => {
const draft = localStorage.getItem('prayerDraft');
if (draft) {
try {
const { date: draftDate, text: draftText, prayFor: draftPrayFor } = JSON.parse(draft);
setDate(draftDate || new Date().toISOString().split('T')[0]);
setText(draftText || '');
setPrayFor(draftPrayFor || '');
} catch (e) {
console.error('Error loading draft:', e);
}
}
}, []);
// Save draft to localStorage
useEffect(() => {
localStorage.setItem('prayerDraft', JSON.stringify({ date, text, prayFor }));
}, [date, text, prayFor]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) {
toast.error('Please log in to save prayers');
return;
}
if (!text.trim()) {
toast.error('Please write something');
return;
}
try {
const prayerData: any = {
userId: user.uid,
date,
text,
answered,
};
// Only add optional fields if they have values
if (prayFor) {
prayerData.prayFor = prayFor;
}
if (answerDetails) {
prayerData.answerDetails = answerDetails;
}
if (editingPrayer) {
// Update existing prayer
await updateDoc(doc(db, 'prayers', editingPrayer.id), prayerData);
setPrayers(prev =>
prev.map(p => (p.id === editingPrayer.id ? { ...p, ...prayerData } : p))
);
toast.success('Prayer updated! 🙏');
setEditingPrayer(null);
} else {
// Add new prayer
const docRef = await addDoc(collection(db, 'prayers'), prayerData);
const newPrayer: Prayer = { id: docRef.id, ...prayerData };
setPrayers(prev => [...prev, newPrayer]);
toast.success('Prayer saved! 🙏');
}
// Reset form
setDate(new Date().toISOString().split('T')[0]);
setText('');
setPrayFor('');
localStorage.removeItem('prayerDraft');
} catch (error) {
console.error('Error saving prayer:', error);
toast.error('Failed to save prayer');
}
};
const handleSelectPrayer = (prayer: Prayer) => {
setEditingPrayer(prayer);
setDate(prayer.date);
setText(prayer.text);
setPrayFor(prayer.prayFor || '');
setAnswered(prayer.answered || false);
setAnswerDetails(prayer.answerDetails || '');
setActiveTab('compose');
};
const handleDateClick = (clickedDate: string) => {
const dayPrayers = prayers.filter(p => p.date === clickedDate);
if (dayPrayers.length > 0) {
handleSelectPrayer(dayPrayers[0]);
} else {
setDate(clickedDate);
setActiveTab('home');
}
};
const handleMarkPrayed = async (prayerId: string) => {
if (!user) return;
try {
const today = new Date().toISOString().split('T')[0];
await updateDoc(doc(db, 'prayers', prayerId), {
lastPrayedFor: today,
});
setPrayers(prev =>
prev.map(p => (p.id === prayerId ? { ...p, lastPrayedFor: today } : p))
);
toast.success('Prayed! 🙏');
} catch (error) {
console.error('Error marking prayed:', error);
toast.error('Failed to update');
}
};
if (authLoading) {
return (
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-black to-slate-800 flex items-center justify-center p-4">
<div className="text-center">
<div className="mb-8 relative">
<div className="w-24 h-24 mx-auto relative">
<div className="absolute inset-0 border-4 border-blue-500/30 rounded-full animate-spin"></div>
<div
className="absolute inset-2 border-4 border-transparent border-t-blue-400 rounded-full animate-spin"
style={{ animationDirection: 'reverse', animationDuration: '1.5s' }}
></div>
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-3xl animate-pulse">🙏</div>
</div>
</div>
</div>
<h1 className="text-2xl font-bold text-white mb-2 animate-pulse">Haver</h1>
<p className="text-gray-400 text-lg mb-4">Preparing your sacred space...</p>
<div className="max-w-xs mx-auto">
<blockquote className="text-gray-300 italic text-sm leading-relaxed">
"Be anxious for nothing, but in everything by prayer and supplication, with thanksgiving, let your requests be made known to God."
</blockquote>
<cite className="text-gray-500 text-xs mt-2 block">— Philippians 4:6</cite>
</div>
<div className="flex justify-center space-x-2 mt-8">
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" style={{ animationDelay: '0s' }}></div>
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" style={{ animationDelay: '0.2s' }}></div>
<div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse" style={{ animationDelay: '0.4s' }}></div>
</div>
</div>
</div>
);
}
if (!user) {
return <Login />;
}
return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 md:pb-0" style={{ paddingBottom: 'calc(6rem + var(--safe-area-inset-bottom))' }}>
<ReminderBanner userEmail={user.email || undefined} />
<BottomNav activeTab={activeTab} onTabChange={setActiveTab} />
{activeTab === 'home' && (
<HomePage
prayers={prayers}
todayCount={prayers.filter(p => p.date === new Date().toISOString().split('T')[0]).length}
onStartPraying={() => setActiveTab('compose')}
onSelectPrayer={handleSelectPrayer}
userName={user.displayName || user.email || undefined}
/>
)}
{activeTab === 'compose' && (
<DailyEntry
date={date}
onDateChange={setDate}
text={text}
onTextChange={setText}
prayFor={prayFor}
onPrayForChange={setPrayFor}
answered={answered}
onAnsweredChange={setAnswered}
answerDetails={answerDetails}
onAnswerDetailsChange={setAnswerDetails}
onSubmit={handleSubmit}
isEditing={!!editingPrayer}
/>
)}
{activeTab === 'journal' && (
<CalendarView prayers={prayers} onDayClick={handleDateClick} />
)}
{activeTab === 'answered' && (
<AnsweredPrayers prayers={prayers} onEdit={handleSelectPrayer} />
)}
{activeTab === 'praying' && (
<ThingsToPrayFor prayers={prayers.filter(p => !p.answered)} onEdit={handleSelectPrayer} onMarkPrayed={handleMarkPrayed} />
)}
{activeTab === 'reflections' && (
<ReflectionsList
prayers={prayers}
onPrayerClick={handleSelectPrayer}
onEdit={handleSelectPrayer}
/>
)}
{activeTab === 'about' && <AboutPage userEmail={user.email || undefined} />}
</div>
);
}