diff --git a/firestore.rules b/firestore.rules index 93dd9bd..a9fb856 100644 --- a/firestore.rules +++ b/firestore.rules @@ -4,6 +4,21 @@ service cloud.firestore { // Allow authenticated users to read/write their own data match /users/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; + + // Allow users to read/write their own statistics + match /statistics/{document=**} { + allow read, write: if request.auth != null && request.auth.uid == userId; + } + + // Allow users to read/write their own settings + match /settings/{document=**} { + allow read, write: if request.auth != null && request.auth.uid == userId; + } + + // Allow users to read/write their own drafts + match /drafts/{document=**} { + allow read, write: if request.auth != null && request.auth.uid == userId; + } } // Allow public reading of answered prayers for testimony wall diff --git a/src/app/page.tsx b/src/app/page.tsx index 5710baf..4717c52 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -13,7 +13,7 @@ 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 { collection, addDoc, getDocs, updateDoc, query, where, doc, setDoc, getDoc } from 'firebase/firestore'; import { Loader2 } from 'lucide-react'; import toast from 'react-hot-toast'; @@ -26,13 +26,16 @@ interface Prayer { answered?: boolean; answerDetails?: string; prayFrequency?: number; // 1-7 times per week - lastPrayedFor?: string; // ISO date string + lastPrayedFor?: string; // ISO date string - kept for backward compatibility + prayedDates?: string[]; // Array of dates when prayed for } export default function Home() { const [activeTab, setActiveTab] = useState<'home' | 'compose' | 'journal' | 'answered' | 'praying' | 'reflections' | 'about'>('home'); const [prayers, setPrayers] = useState([]); const [editingPrayer, setEditingPrayer] = useState(null); + const [prayersLoading, setPrayersLoading] = useState(false); + const [mostPrayedFor, setMostPrayedFor] = useState(''); // Form state const [date, setDate] = useState(new Date().toISOString().split('T')[0]); @@ -45,8 +48,13 @@ export default function Home() { // Load prayers from Firestore useEffect(() => { - if (!user) return; + if (!user) { + setPrayers([]); + setPrayersLoading(false); + return; + } + setPrayersLoading(true); const loadPrayers = async () => { try { const prayersRef = collection(db, 'prayers'); @@ -62,31 +70,61 @@ export default function Home() { } catch (error) { console.error('Error loading prayers:', error); toast.error('Failed to load prayers'); + } finally { + setPrayersLoading(false); } }; loadPrayers(); + fetchPrayerStatistics(); }, [user]); - // Load draft from localStorage + // Load draft from Firebase 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); - } - } - }, []); + if (!user) return; - // Save draft to localStorage + const loadDraft = async () => { + try { + const draftRef = doc(db, 'users', user.uid, 'drafts', 'currentPrayer'); + const draftSnap = await getDoc(draftRef); + + if (draftSnap.exists()) { + const draftData = draftSnap.data(); + setDate(draftData.date || new Date().toISOString().split('T')[0]); + setText(draftData.text || ''); + setPrayFor(draftData.prayFor || ''); + } + } catch (error: any) { + // Silently fail if offline + if (error?.code !== 'unavailable') { + console.error('Error loading draft:', error); + } + } + }; + + loadDraft(); + }, [user]); + + // Save draft to Firebase useEffect(() => { - localStorage.setItem('prayerDraft', JSON.stringify({ date, text, prayFor })); - }, [date, text, prayFor]); + if (!user) return; + + const saveDraft = async () => { + try { + const draftRef = doc(db, 'users', user.uid, 'drafts', 'currentPrayer'); + await setDoc(draftRef, { date, text, prayFor, updatedAt: new Date().toISOString() }, { merge: true }); + } catch (error: any) { + // Silently fail if offline - will sync when connection is restored + if (error?.code !== 'unavailable') { + console.error('Error saving draft:', error); + } + } + }; + + // Debounce the save to avoid too many writes + const timeoutId = setTimeout(saveDraft, 1000); + return () => clearTimeout(timeoutId); + }, [date, text, prayFor, user]); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -137,7 +175,12 @@ export default function Home() { setDate(new Date().toISOString().split('T')[0]); setText(''); setPrayFor(''); - localStorage.removeItem('prayerDraft'); + + // Clear draft from Firebase + if (user) { + const draftRef = doc(db, 'users', user.uid, 'drafts', 'currentPrayer'); + await setDoc(draftRef, { date: new Date().toISOString().split('T')[0], text: '', prayFor: '' }); + } } catch (error) { console.error('Error saving prayer:', error); toast.error('Failed to save prayer'); @@ -164,17 +207,86 @@ export default function Home() { } }; + const updatePrayerStatistics = async (prayFor: string) => { + if (!user) return; + + try { + const statsDocRef = doc(db, 'users', user.uid, 'statistics', 'prayerStats'); + + // Get current stats + let stats: Record = {}; + try { + const statsSnap = await getDoc(statsDocRef); + if (statsSnap.exists()) { + stats = statsSnap.data() as Record; + } + } catch (e) { + // Stats don't exist yet + } + + // Increment count for this prayFor item + stats[prayFor] = (stats[prayFor] || 0) + 1; + + // Update the stats document + await setDoc(statsDocRef, stats, { merge: true }); + + // Update local state + const sorted = Object.entries(stats).sort((a, b) => b[1] - a[1]); + setMostPrayedFor(sorted.length > 0 ? sorted[0][0] : ''); + } catch (error) { + console.error('Error updating statistics:', error); + } + }; + + const fetchPrayerStatistics = async () => { + if (!user) return; + + try { + const statsDocRef = doc(db, 'users', user.uid, 'statistics', 'prayerStats'); + const statsSnap = await getDoc(statsDocRef); + + if (statsSnap.exists()) { + const stats = statsSnap.data() as Record; + const sorted = Object.entries(stats).sort((a, b) => b[1] - a[1]); + setMostPrayedFor(sorted[0][0]); + } + } catch (error: any) { + // Silently fail if offline - will retry when connection is restored + if (error?.code !== 'unavailable') { + console.error('Error fetching statistics:', error); + } + } + }; + const handleMarkPrayed = async (prayerId: string) => { if (!user) return; try { const today = new Date().toISOString().split('T')[0]; + const prayer = prayers.find(p => p.id === prayerId); + + if (!prayer) return; + + // Get existing prayed dates or initialize array + const existingDates = prayer.prayedDates || []; + + // Add today's date if not already in the array + const updatedDates = existingDates.includes(today) + ? existingDates + : [...existingDates, today]; + await updateDoc(doc(db, 'prayers', prayerId), { lastPrayedFor: today, + prayedDates: updatedDates, }); + // Update statistics if this prayer has a prayFor value + if (prayer.prayFor) { + await updatePrayerStatistics(prayer.prayFor); + } + setPrayers(prev => - prev.map(p => (p.id === prayerId ? { ...p, lastPrayedFor: today } : p)) + prev.map(p => (p.id === prayerId ? { ...p, lastPrayedFor: today, prayedDates: updatedDates } : p)) ); toast.success('Prayed!'); } catch (error) { @@ -183,38 +295,70 @@ export default function Home() { } }; - if (authLoading) { + if (authLoading || prayersLoading) { + const loadingMessage = authLoading ? 'Preparing your sacred space...' : 'Loading your prayers...'; + return ( -
-
+
+ {/* Animated Background */} +
+
+
+
+
+ +
+ {/* Animated Icon */}
-
-
-
+ {/* Outer rotating ring */} +
+
+ + {/* Middle rotating ring - reverse direction */} +
+
+ + {/* Inner pulsing circle */} +
+ + {/* App Icon */}
- + Haver icon
- Haver icon -

Haver

-

Preparing your sacred space...

+ {/* App Title */} +

+ Haver +

+ + {/* Loading Message */} +

+ {loadingMessage} +

-
-
+ {/* Inspirational Quote */} +
+
"Be anxious for nothing, but in everything by prayer and supplication, with thanksgiving, let your requests be made known to God."
- — Philippians 4:6 + — Philippians 4:6
-
-
-
-
+ {/* Animated Dots */} +
+
+
+
@@ -237,6 +381,7 @@ export default function Home() { onStartPraying={() => setActiveTab('compose')} onSelectPrayer={handleSelectPrayer} userName={user.displayName || user.email || undefined} + mostPrayedFor={mostPrayedFor} /> )} diff --git a/src/components/AboutPage.tsx b/src/components/AboutPage.tsx index a98c576..c61f6d4 100644 --- a/src/components/AboutPage.tsx +++ b/src/components/AboutPage.tsx @@ -4,6 +4,8 @@ import React, { useState } from 'react'; import { useAuth } from '@/context/AuthContext'; import { Info, Smartphone, BellRing } from 'lucide-react'; import { APP_VERSION, WHATS_NEW_BRIEF } from '@/lib/version'; +import { db } from '@/lib/firebase'; +import { doc, getDoc, setDoc } from 'firebase/firestore'; interface AboutPageProps { userEmail?: string; @@ -55,49 +57,86 @@ export default function AboutPage({ userEmail }: AboutPageProps) { setNotificationPermission(Notification.permission); } - // Load saved settings - const savedEnabled = localStorage.getItem('prayerRemindersEnabled') === 'true'; - const savedTime = localStorage.getItem('prayerReminderTime') || '09:00'; - setNotificationsEnabled(savedEnabled); - setReminderTime(savedTime); - - // Schedule notification for supported platforms - if (savedEnabled && webPushSupport) { - scheduleNotification(); - } - }, []); + // Load saved settings from Firebase + const loadSettings = async () => { + if (!user) return; + + try { + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + const settingsSnap = await getDoc(settingsRef); + + if (settingsSnap.exists()) { + const settings = settingsSnap.data(); + setNotificationsEnabled(settings.enabled || false); + setReminderTime(settings.time || '09:00'); + + if (settings.enabled && webPushSupport) { + scheduleNotification(); + } + } + } catch (error: any) { + if (error?.code !== 'unavailable') { + console.error('Error loading settings:', error); + } + } + }; + + loadSettings(); + }, [user]); const requestNotificationPermission = async () => { - if (!supportsWebPush) return; + if (!supportsWebPush || !user) return; if ('Notification' in window) { const permission = await Notification.requestPermission(); setNotificationPermission(permission); if (permission === 'granted') { setNotificationsEnabled(true); - localStorage.setItem('prayerRemindersEnabled', 'true'); - localStorage.setItem('lastPrayerReminderDate', new Date().toISOString()); + + // Save to Firebase + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + await setDoc(settingsRef, { + enabled: true, + time: reminderTime, + lastReminderDate: new Date().toISOString() + }, { merge: true }); + scheduleNotification(); } } }; const toggleNotifications = async () => { + if (!user) return; + if (supportsWebPush) { if (!notificationsEnabled && notificationPermission !== 'granted') { await requestNotificationPermission(); } else { setNotificationsEnabled(!notificationsEnabled); - localStorage.setItem('prayerRemindersEnabled', String(!notificationsEnabled)); + + // Save to Firebase + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + await setDoc(settingsRef, { + enabled: !notificationsEnabled, + time: reminderTime, + lastReminderDate: new Date().toISOString() + }, { merge: true }); + if (!notificationsEnabled) { - localStorage.setItem('lastPrayerReminderDate', new Date().toISOString()); scheduleNotification(); } } } else { // For iOS and unsupported platforms, just enable in-app reminders setNotificationsEnabled(!notificationsEnabled); - localStorage.setItem('prayerRemindersEnabled', String(!notificationsEnabled)); + + // Save to Firebase + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + await setDoc(settingsRef, { + enabled: !notificationsEnabled, + time: reminderTime + }, { merge: true }); } }; @@ -113,15 +152,24 @@ export default function AboutPage({ userEmail }: AboutPageProps) { }; const handleTimeChange = (newTime: string) => { + if (!user) return; + setReminderTime(newTime); - localStorage.setItem('prayerReminderTime', newTime); + + // Save to Firebase + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + setDoc(settingsRef, { + enabled: notificationsEnabled, + time: newTime + }, { merge: true }); + if (notificationsEnabled) { scheduleNotification(); } }; const scheduleNotification = () => { - if (!supportsWebPush || !notificationsEnabled) return; + if (!supportsWebPush || !notificationsEnabled || !user) return; // Calculate time until next reminder const [hours, minutes] = reminderTime.split(':').map(Number); @@ -139,7 +187,7 @@ export default function AboutPage({ userEmail }: AboutPageProps) { // Only schedule if we have a valid time difference if (timeUntilNotification > 0) { setTimeout(() => { - if ('Notification' in window && Notification.permission === 'granted' && notificationsEnabled) { + if ('Notification' in window && Notification.permission === 'granted' && notificationsEnabled && user) { new Notification('Time to Pray', { body: 'Take a moment to connect with God and pray.', icon: '/icon-192x192.png', @@ -148,8 +196,11 @@ export default function AboutPage({ userEmail }: AboutPageProps) { requireInteraction: false, }); - // Save reminder shown timestamp - localStorage.setItem('lastPrayerReminderDate', new Date().toISOString()); + // Save reminder shown timestamp to Firebase + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + setDoc(settingsRef, { + lastReminderDate: new Date().toISOString() + }, { merge: true }); // Reschedule for next day setTimeout(scheduleNotification, 24 * 60 * 60 * 1000); diff --git a/src/components/AnsweredPrayers.tsx b/src/components/AnsweredPrayers.tsx index 98f510e..73557b6 100644 --- a/src/components/AnsweredPrayers.tsx +++ b/src/components/AnsweredPrayers.tsx @@ -10,6 +10,7 @@ interface Prayer { answerDetails?: string; prayFrequency?: number; // 1-7 times per week lastPrayedFor?: string; // ISO date string + prayedDates?: string[]; // Array of dates when prayed for } interface AnsweredPrayersProps { diff --git a/src/components/CalendarView.tsx b/src/components/CalendarView.tsx index e7d9d57..6d27778 100644 --- a/src/components/CalendarView.tsx +++ b/src/components/CalendarView.tsx @@ -9,6 +9,7 @@ interface Prayer { text: string; prayFor?: string; lastPrayedFor?: string; + prayedDates?: string[]; } interface CalendarViewProps { @@ -24,13 +25,19 @@ export default function CalendarView({ prayers, onDayClick }: CalendarViewProps) }, [prayers]); const daysWithPrayedFor = useMemo(() => { - const dates = new Set(); + const dateCounts = new Map(); prayers.forEach(p => { - if (p.lastPrayedFor) { - dates.add(p.lastPrayedFor); + // Check both old lastPrayedFor and new prayedDates array + if (p.prayedDates && p.prayedDates.length > 0) { + p.prayedDates.forEach(date => { + dateCounts.set(date, (dateCounts.get(date) || 0) + 1); + }); + } else if (p.lastPrayedFor) { + // Backward compatibility with old format + dateCounts.set(p.lastPrayedFor, (dateCounts.get(p.lastPrayedFor) || 0) + 1); } }); - return dates; + return dateCounts; }, [prayers]); const getDaysInMonth = (date: Date) => { @@ -120,7 +127,7 @@ export default function CalendarView({ prayers, onDayClick }: CalendarViewProps) ? prayers.filter(p => p.date === dateString).length : 0; const prayedForCount = dateString - ? prayers.filter(p => p.lastPrayedFor === dateString).length + ? daysWithPrayedFor.get(dateString) || 0 : 0; // Determine color based on activity type diff --git a/src/components/Clock.tsx b/src/components/Clock.tsx index 11848f9..b6e1235 100644 --- a/src/components/Clock.tsx +++ b/src/components/Clock.tsx @@ -8,7 +8,7 @@ export default function Clock() { useEffect(() => { // Set initial time setTime(new Date().toLocaleTimeString('en-US', { - hour: '2-digit', + hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true @@ -17,7 +17,7 @@ export default function Clock() { // Update time every second const interval = setInterval(() => { setTime(new Date().toLocaleTimeString('en-US', { - hour: '2-digit', + hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true diff --git a/src/components/DailyEntry.tsx b/src/components/DailyEntry.tsx index 64b1a38..2f65ba0 100644 --- a/src/components/DailyEntry.tsx +++ b/src/components/DailyEntry.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import toast from 'react-hot-toast'; -import { PenSquare, Calendar as CalendarIcon, HandHeart, CheckCircle2, Sparkles, Save, Loader2 } from 'lucide-react'; +import { PenSquare, Calendar as CalendarIcon, HandHeart, CheckCircle2, Sparkles, Save, Loader2, ChevronDown } from 'lucide-react'; interface DailyEntryProps { date: string; @@ -85,14 +85,19 @@ export default function DailyEntry({ {/* Pray For */}
- onPrayForChange(e.target.value)} - placeholder="Person, situation, or concern..." - list="prayfor-options" - className="w-full px-4 py-3 rounded-xl bg-slate-800/60 backdrop-blur-xl border border-slate-700/50 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" - /> +
+ onPrayForChange(e.target.value)} + placeholder="Person, situation, or concern..." + list="prayfor-options" + className="w-full px-4 py-3.5 pr-12 rounded-xl bg-slate-800/60 backdrop-blur-xl border border-slate-700/50 text-white placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all" + /> + {prayForOptions.length > 0 && ( + + )} +
{prayForOptions.length > 0 && ( {prayForOptions.map((opt) => ( diff --git a/src/components/HomePage.tsx b/src/components/HomePage.tsx index 9d58e2e..7a72ac7 100644 --- a/src/components/HomePage.tsx +++ b/src/components/HomePage.tsx @@ -14,6 +14,7 @@ interface Prayer { answerDetails?: string; prayFrequency?: number; // 1-7 times per week lastPrayedFor?: string; // ISO date string + prayedDates?: string[]; // Array of dates when prayed for } interface HomePageProps { @@ -22,9 +23,10 @@ interface HomePageProps { onStartPraying: () => void; onSelectPrayer: (prayer: Prayer) => void; userName?: string; + mostPrayedFor?: string; } -export default function HomePage({ prayers, todayCount, onStartPraying, onSelectPrayer, userName }: HomePageProps) { +export default function HomePage({ prayers, todayCount, onStartPraying, onSelectPrayer, userName, mostPrayedFor }: HomePageProps) { const today = new Date().toISOString().split('T')[0]; const todayPrayers = prayers.filter(p => p.date === today); const recentPrayers = prayers.slice(0, 5); @@ -55,7 +57,12 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect const activePrayerCount = prayers.filter(p => !p.answered).length; // Count how many active prayers were prayed for today - const prayedTodayCount = prayers.filter(p => !p.answered && p.lastPrayedFor === today).length; + const prayedTodayCount = prayers.filter(p => + !p.answered && ( + p.prayedDates?.includes(today) || + p.lastPrayedFor === today + ) + ).length; // Calculate actual streak - consecutive days with prayers const calculateStreak = () => { @@ -65,8 +72,13 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect const activityDates = new Set(); prayers.forEach(p => { activityDates.add(p.date); // Date prayer was created - if (p.lastPrayedFor) { - activityDates.add(p.lastPrayedFor); // Date marked as prayed for + + // Add all prayed dates from the array + if (p.prayedDates && p.prayedDates.length > 0) { + p.prayedDates.forEach((date: string) => activityDates.add(date)); + } else if (p.lastPrayedFor) { + // Backward compatibility + activityDates.add(p.lastPrayedFor); } }); @@ -101,15 +113,6 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect ? Math.round((answeredPrayers / totalPrayers) * 100) : 0; - // Get most prayed for person/topic based on lastPrayedFor clicks - const prayerForCounts: Record = {}; - prayers.forEach(p => { - if (p.prayFor && p.lastPrayedFor) { - prayerForCounts[p.prayFor] = (prayerForCounts[p.prayFor] || 0) + 1; - } - }); - const mostPrayedFor = Object.entries(prayerForCounts).sort((a, b) => b[1] - a[1])[0]; - // Recent answered prayers const recentAnswered = prayers.filter(p => p.answered).slice(0, 3); @@ -122,15 +125,8 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect
- {/* Clock */} -
-
- -
-
- {/* Header with Personalized Greeting */} -
+
Haver icon

{getGreeting()}, {getFirstName()}! @@ -151,9 +147,15 @@ export default function HomePage({ prayers, todayCount, onStartPraying, onSelect All caught up! Start a new prayer to keep your journey going.

)} -

+

{new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}

+ {/* Clock - Below Date */} +
+
+ +
+

{/* Stats Grid */} diff --git a/src/components/ReflectionsList.tsx b/src/components/ReflectionsList.tsx index 70a4f66..57ccbd2 100644 --- a/src/components/ReflectionsList.tsx +++ b/src/components/ReflectionsList.tsx @@ -12,6 +12,7 @@ interface Prayer { answered?: boolean; prayFrequency?: number; // 1-7 times per week lastPrayedFor?: string; // ISO date string + prayedDates?: string[]; // Array of dates when prayed for } interface PrayerListProps { diff --git a/src/components/ReminderBanner.tsx b/src/components/ReminderBanner.tsx index a8880fc..9032998 100644 --- a/src/components/ReminderBanner.tsx +++ b/src/components/ReminderBanner.tsx @@ -2,6 +2,9 @@ import React, { useState, useEffect } from 'react'; import { BellRing, X } from 'lucide-react'; +import { useAuth } from '@/context/AuthContext'; +import { db } from '@/lib/firebase'; +import { doc, getDoc, setDoc } from 'firebase/firestore'; interface ReminderBannerProps { userEmail?: string; @@ -11,47 +14,70 @@ export default function ReminderBanner({ userEmail }: ReminderBannerProps) { const [showBanner, setShowBanner] = useState(false); const [reminderTime, setReminderTime] = useState('09:00'); const [isTime, setIsTime] = useState(false); + const { user } = useAuth(); useEffect(() => { - // Check if reminders are enabled and if it's reminder time - const remindersEnabled = localStorage.getItem('prayerRemindersEnabled') === 'true'; - const savedTime = localStorage.getItem('prayerReminderTime') || '09:00'; - const lastReminderDate = localStorage.getItem('lastPrayerReminderDate'); - - setReminderTime(savedTime); + if (!user) return; - if (!remindersEnabled) return; + // Load settings from Firebase + const loadSettingsAndCheck = async () => { + try { + const settingsRef = doc(db, 'users', user.uid, 'settings', 'notifications'); + const settingsSnap = await getDoc(settingsRef); + + if (!settingsSnap.exists()) return; + + const settings = settingsSnap.data(); + const remindersEnabled = settings.enabled || false; + const savedTime = settings.time || '09:00'; + const lastReminderDate = settings.lastReminderDate; + + setReminderTime(savedTime); - // Check if we should show the banner - const checkIfTimeToRemind = () => { - const now = new Date(); - const [hours, minutes] = savedTime.split(':').map(Number); - const reminderTodayTime = new Date(); - reminderTodayTime.setHours(hours, minutes, 0, 0); + if (!remindersEnabled) return; - // Check if current time is within 5 minutes of reminder time - const timeDiff = Math.abs(now.getTime() - reminderTodayTime.getTime()); - const withinWindow = timeDiff < 5 * 60 * 1000; // 5 minute window + // Check if we should show the banner + const checkIfTimeToRemind = () => { + const now = new Date(); + const [hours, minutes] = savedTime.split(':').map(Number); + const reminderTodayTime = new Date(); + reminderTodayTime.setHours(hours, minutes, 0, 0); - if (withinWindow) { - // Check if we already showed reminder today - const today = new Date().toISOString().split('T')[0]; - const lastDate = lastReminderDate?.split('T')[0]; + // Check if current time is within 5 minutes of reminder time + const timeDiff = Math.abs(now.getTime() - reminderTodayTime.getTime()); + const withinWindow = timeDiff < 5 * 60 * 1000; // 5 minute window - if (lastDate !== today) { - setIsTime(true); - setShowBanner(true); - localStorage.setItem('lastPrayerReminderDate', new Date().toISOString()); + if (withinWindow) { + // Check if we already showed reminder today + const today = new Date().toISOString().split('T')[0]; + const lastDate = lastReminderDate?.split('T')[0]; + + if (lastDate !== today) { + setIsTime(true); + setShowBanner(true); + + // Update last reminder date in Firebase + setDoc(settingsRef, { + lastReminderDate: new Date().toISOString() + }, { merge: true }); + } + } + }; + + checkIfTimeToRemind(); + + // Check every minute + const interval = setInterval(checkIfTimeToRemind, 60000); + return () => clearInterval(interval); + } catch (error: any) { + if (error?.code !== 'unavailable') { + console.error('Error loading reminder settings:', error); } } }; - checkIfTimeToRemind(); - - // Check every minute - const interval = setInterval(checkIfTimeToRemind, 60000); - return () => clearInterval(interval); - }, []); + loadSettingsAndCheck(); + }, [user]); if (!showBanner) return null; diff --git a/src/components/ThingsToPrayFor.tsx b/src/components/ThingsToPrayFor.tsx index 3a10acd..d51664d 100644 --- a/src/components/ThingsToPrayFor.tsx +++ b/src/components/ThingsToPrayFor.tsx @@ -13,6 +13,7 @@ interface Prayer { answerDetails?: string; prayFrequency?: number; // 1-7 times per week lastPrayedFor?: string; // ISO date string + prayedDates?: string[]; // Array of dates when prayed for } interface ThingsToPrayForProps { @@ -85,7 +86,7 @@ export default function ThingsToPrayFor({ prayers, onEdit, onMarkPrayed }: Thing (new Date().getTime() - new Date(prayer.date).getTime()) / (1000 * 60 * 60 * 24) ); const today = new Date().toISOString().split('T')[0]; - const prayedToday = prayer.lastPrayedFor === today; + const prayedToday = prayer.prayedDates?.includes(today) || prayer.lastPrayedFor === today; return (
{ - // Simple auth state listener - no messaging complexity let mounted = true; + let authResolved = false; const unsubscribe = onAuthStateChanged(auth, (user) => { if (mounted) { @@ -44,22 +44,36 @@ export function AuthProvider({ children }: { children: ReactNode }) { signOut(auth); setUser(null); setLoading(false); + authResolved = true; return; } } } - setUser(user); - setLoading(false); + // If we get null on first call, wait a bit for session restoration + if (!user && !authResolved) { + setTimeout(() => { + if (mounted && !authResolved) { + setUser(null); + setLoading(false); + authResolved = true; + } + }, 1000); + } else { + setUser(user); + setLoading(false); + authResolved = true; + } } }); - // Timeout safety: force loading to false after 2 seconds + // Safety timeout: force loading to false after 4 seconds const timeout = setTimeout(() => { - if (mounted) { + if (mounted && !authResolved) { setLoading(false); + authResolved = true; } - }, 2000); + }, 4000); return () => { mounted = false; diff --git a/src/lib/firebase.ts b/src/lib/firebase.ts index 087b05e..4e6f783 100644 --- a/src/lib/firebase.ts +++ b/src/lib/firebase.ts @@ -1,7 +1,7 @@ // Import the functions you need from the SDKs you need import { initializeApp } from 'firebase/app'; import { getAuth } from 'firebase/auth'; -import { getFirestore } from 'firebase/firestore'; +import { getFirestore, enableIndexedDbPersistence, enableMultiTabIndexedDbPersistence } from 'firebase/firestore'; import { getStorage } from 'firebase/storage'; import { getAnalytics } from 'firebase/analytics'; import { getMessaging, onMessage, getToken } from 'firebase/messaging'; @@ -29,6 +29,19 @@ export const auth = getAuth(app); // Initialize Cloud Firestore and get a reference to the service export const db = getFirestore(app); +// Enable offline persistence +if (typeof window !== 'undefined') { + enableMultiTabIndexedDbPersistence(db).catch((err) => { + if (err.code === 'failed-precondition') { + // Multiple tabs open, persistence can only be enabled in one tab at a time + console.warn('Firestore: Multiple tabs open, persistence disabled'); + } else if (err.code === 'unimplemented') { + // The current browser doesn't support persistence + console.warn('Firestore: Persistence not supported in this browser'); + } + }); +} + // Initialize Firebase Storage and get a reference to the service export const storage = getStorage(app);