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

1029 lines
40 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { useAuth } from '@/context/AuthContext';
import Login from '@/components/Login';
import Admin from '@/components/Admin';
import Profile from '@/components/Profile';
import { db } from '@/lib/firebase';
import { doc, setDoc, collection, addDoc, getDocs, updateDoc, query, where, getDoc } from 'firebase/firestore';
import emailjs from '@emailjs/browser';
import jsPDF from 'jspdf';
import { CSVLink } from 'react-csv';
interface Prayer {
id: string;
userId?: string;
date: string;
type: 'prayer' | 'praise';
text: string;
journal: string;
email: string;
prayFor: string;
archived?: boolean;
reminderFrequency?: 'daily' | 'weekly' | 'monthly' | 'never';
includeActiveSummary?: boolean;
answered?: boolean;
answeredDate?: string;
}
export default function Home() {
const [prayers, setPrayers] = useState<Prayer[]>([]);
const [sharedPrayers, setSharedPrayers] = useState<Prayer[]>([]);
const [form, setForm] = useState({
date: new Date().toISOString().split('T')[0],
type: 'prayer' as 'prayer' | 'praise',
text: '',
journal: '',
email: '',
prayFor: '',
reminderFrequency: 'never' as 'daily' | 'weekly' | 'monthly' | 'never',
includeActiveSummary: false,
});
const [showAdmin, setShowAdmin] = useState(false);
const [showProfile, setShowProfile] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [activeTab, setActiveTab] = useState<'add' | 'active' | 'archived' | 'search' | 'community'>('add');
const itemsPerPage = 10;
const { user, loading, logout, isAdmin } = useAuth();
const [showLoadingScreen, setShowLoadingScreen] = useState(true);
const [editingPrayer, setEditingPrayer] = useState<Prayer | null>(null);
const [dateFrom, setDateFrom] = useState('');
const [dateTo, setDateTo] = useState('');
const [typeFilter, setTypeFilter] = useState<'all' | 'prayer' | 'praise'>('all');
const [userProfile, setUserProfile] = useState<any>({});
// Ensure loading screen shows for at least 5 seconds
useEffect(() => {
const timer = setTimeout(() => {
setShowLoadingScreen(false);
}, 5000); // 5 seconds minimum
return () => clearTimeout(timer);
}, []);
// Load prayers from Firestore
const loadPrayers = async () => {
if (!user) return;
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);
}
};
// Load shared prayers
const loadSharedPrayers = async () => {
try {
const sharedRef = collection(db, 'sharedPrayers');
const querySnapshot = await getDocs(sharedRef);
const loadedShared: Prayer[] = [];
querySnapshot.forEach((doc) => {
loadedShared.push({ id: doc.id, ...doc.data() } as Prayer);
});
setSharedPrayers(loadedShared);
} catch (error) {
console.error('Error loading shared prayers:', error);
}
};
// Load prayers when user changes
useEffect(() => {
if (user) {
loadPrayers();
} else {
setPrayers([]); // Clear prayers when user logs out
}
}, [user]);
// Load shared prayers on mount
useEffect(() => {
loadSharedPrayers();
}, []);
// Load user profile
useEffect(() => {
const loadUserProfile = async () => {
if (!user) return;
try {
const docRef = doc(db, 'users', user.uid);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
setUserProfile(docSnap.data());
}
} catch (error) {
console.error('Error loading user profile:', error);
}
};
loadUserProfile();
}, [user]);
// Export functions
const exportToPDF = (prayerList: Prayer[], title: string) => {
const doc = new jsPDF();
doc.text(title, 10, 10);
let y = 20;
prayerList.forEach((prayer, index) => {
if (y > 270) {
doc.addPage();
y = 10;
}
doc.text(`${index + 1}. ${prayer.date} - ${prayer.type.toUpperCase()}: ${prayer.text}`, 10, y);
y += 10;
if (prayer.prayFor) {
doc.text(`Praying for: ${prayer.prayFor}`, 10, y);
y += 10;
}
if (prayer.journal) {
doc.text(`Journal: ${prayer.journal}`, 10, y);
y += 10;
}
y += 5;
});
doc.save(`${title.replace(/\s+/g, '_')}.pdf`);
};
const exportToJSON = (prayerList: Prayer[], title: string) => {
const dataStr = JSON.stringify(prayerList, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = `${title.replace(/\s+/g, '_')}.json`;
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
};
const getCSVData = (prayerList: Prayer[]) => {
return prayerList.map(prayer => ({
Date: prayer.date,
Type: prayer.type,
Text: prayer.text,
PrayFor: prayer.prayFor,
Journal: prayer.journal,
Email: prayer.email,
Archived: prayer.archived ? 'Yes' : 'No'
}));
};
// Auto-populate email field with user's email
useEffect(() => {
if (user?.email) {
setForm(prev => ({ ...prev, email: user.email! }));
}
}, [user]);
// Load draft from localStorage
useEffect(() => {
const draft = localStorage.getItem('prayerDraft');
if (draft) {
try {
setForm(JSON.parse(draft));
} catch (e) {
console.error('Error loading draft:', e);
}
}
}, []);
// Save draft to localStorage
useEffect(() => {
localStorage.setItem('prayerDraft', JSON.stringify(form));
}, [form]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) {
alert('Please log in to save prayers');
return;
}
try {
const prayerData = {
userId: user.uid,
date: form.date,
type: form.type,
text: form.text,
journal: form.journal,
email: form.email,
prayFor: form.prayFor,
reminderFrequency: form.reminderFrequency,
includeActiveSummary: form.includeActiveSummary,
archived: editingPrayer ? editingPrayer.archived : false,
};
if (editingPrayer) {
// Update existing prayer
await updateDoc(doc(db, 'prayers', editingPrayer.id), prayerData);
// Update local state
setPrayers(prev => prev.map(p =>
p.id === editingPrayer.id ? { ...p, ...prayerData } : p
));
// Reset editing state
setEditingPrayer(null);
} else {
// Add new prayer
const docRef = await addDoc(collection(db, 'prayers'), prayerData);
// Add to local state with Firestore ID
const newPrayer: Prayer = {
id: docRef.id,
...prayerData,
};
setPrayers(prev => [...prev, newPrayer]);
// Store user reminder preferences in Firestore
if (form.reminderFrequency !== 'never') {
await setDoc(doc(db, 'users', user.uid), {
email: form.email,
reminderFrequency: form.reminderFrequency,
includeActiveSummary: form.includeActiveSummary,
lastEmailSent: null,
}, { merge: true });
console.log('✅ Reminder preferences saved');
}
// Send immediate email using EmailJS if reminder frequency is set
if (form.reminderFrequency !== 'never' && form.email) {
try {
// EmailJS configuration - you'll need to set these up at emailjs.com
const serviceId = process.env.NEXT_PUBLIC_EMAILJS_SERVICE_ID || 'your_service_id';
const templateId = process.env.NEXT_PUBLIC_EMAILJS_TEMPLATE_ID || 'your_template_id';
const publicKey = process.env.NEXT_PUBLIC_EMAILJS_PUBLIC_KEY || 'your_public_key';
const templateParams = {
to_email: form.email,
from_name: 'Prayer App',
to_name: user.displayName || user.email?.split('@')[0] || 'Prayer User',
subject: `New ${form.type} added - ${form.reminderFrequency} reminders enabled`,
message: `Date: ${form.date}\n\n${form.text}\n\nJournal: ${form.journal || 'None'}${form.prayFor ? `\n\nPraying for: ${form.prayFor}` : ''}`,
prayer_type: form.type,
prayer_date: form.date,
prayer_text: form.text,
prayer_journal: form.journal || 'None',
praying_for: form.prayFor || 'Not specified',
reminder_frequency: form.reminderFrequency,
};
// Only send if EmailJS is properly configured
if (serviceId !== 'your_service_id' && templateId !== 'your_template_id' && publicKey !== 'your_public_key') {
await emailjs.send(serviceId, templateId, templateParams, publicKey);
console.log('✅ Email sent via EmailJS');
} else {
console.log('ℹ️ EmailJS not configured - email sending skipped (this is normal)');
}
} catch (emailError) {
console.error('❌ EmailJS error:', emailError);
// Don't fail the prayer submission if email fails
}
}
}
// Reset form
setForm({
date: new Date().toISOString().split('T')[0],
type: 'prayer',
text: '',
journal: '',
email: user.email || '',
prayFor: '',
reminderFrequency: 'never',
includeActiveSummary: false,
});
} catch (error) {
console.error('Error saving prayer:', error);
alert('Failed to save prayer. Please try again.');
}
};
const activePrayers = prayers.filter(prayer => !prayer.archived);
const archivedPrayers = prayers.filter(prayer => prayer.archived);
// Enhanced filtering function
const applyFilters = (prayerList: Prayer[]) => {
return prayerList.filter(prayer => {
// Keyword search
const matchesSearch = !searchTerm ||
prayer.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
prayer.journal.toLowerCase().includes(searchTerm.toLowerCase()) ||
prayer.type.toLowerCase().includes(searchTerm.toLowerCase()) ||
prayer.prayFor.toLowerCase().includes(searchTerm.toLowerCase());
// Date range filter
const prayerDate = new Date(prayer.date);
const fromDate = dateFrom ? new Date(dateFrom) : null;
const toDateFilter = dateTo ? new Date(dateTo) : null;
const matchesDateFrom = !fromDate || prayerDate >= fromDate;
const matchesDateTo = !toDateFilter || prayerDate <= toDateFilter;
// Type filter
const matchesType = typeFilter === 'all' || prayer.type === typeFilter;
return matchesSearch && matchesDateFrom && matchesDateTo && matchesType;
});
};
// Apply filters to each prayer list
const filteredActivePrayers = applyFilters(activePrayers);
const filteredArchivedPrayers = applyFilters(archivedPrayers);
// For search tab, combine all filtered prayers
const allPrayers = [...activePrayers, ...archivedPrayers];
const filteredSearchPrayers = applyFilters(allPrayers);
// Pagination logic
const totalActivePages = Math.ceil(filteredActivePrayers.length / itemsPerPage);
const totalArchivedPages = Math.ceil(filteredArchivedPrayers.length / itemsPerPage);
const totalSearchPages = Math.ceil(filteredSearchPrayers.length / itemsPerPage);
const paginatedActivePrayers = filteredActivePrayers.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
const paginatedArchivedPrayers = filteredArchivedPrayers.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
const paginatedSearchPrayers = filteredSearchPrayers.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage
);
const currentPrayers = activeTab === 'active' ? paginatedActivePrayers :
activeTab === 'archived' ? paginatedArchivedPrayers :
activeTab === 'search' ? paginatedSearchPrayers :
sharedPrayers; // Community
const currentTotalPages = activeTab === 'active' ? totalActivePages :
activeTab === 'archived' ? totalArchivedPages :
activeTab === 'search' ? totalSearchPages :
1; // Community
const groupedPrayers = currentPrayers.reduce((acc, prayer) => {
if (!acc[prayer.date]) acc[prayer.date] = [];
acc[prayer.date].push(prayer);
return acc;
}, {} as Record<string, Prayer[]>);
const sharePrayer = async (prayer: Prayer) => {
const shareText = `${prayer.type.toUpperCase()}: ${prayer.text}${prayer.journal ? `\n\nJournal: ${prayer.journal}` : ''}`;
if (navigator.share) {
try {
await navigator.share({
title: `Shared ${prayer.type}`,
text: shareText,
});
// Show success feedback
const button = document.activeElement as HTMLElement;
if (button) {
const originalText = button.textContent;
button.textContent = '✅ Shared!';
button.classList.add('bg-green-600', 'hover:bg-green-700');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('bg-green-600', 'hover:bg-green-700');
}, 2000);
}
} catch (err) {
console.log('Share cancelled');
}
} else {
// Fallback: copy to clipboard
try {
await navigator.clipboard.writeText(shareText);
// Show success feedback
const button = document.activeElement as HTMLElement;
if (button) {
const originalText = button.textContent;
button.textContent = '📋 Copied!';
button.classList.add('bg-green-600', 'hover:bg-green-700');
setTimeout(() => {
button.textContent = originalText;
button.classList.remove('bg-green-600', 'hover:bg-green-700');
}, 2000);
}
} catch (clipboardErr) {
alert('Prayer copied to clipboard!');
}
}
};
const shareToCommunity = async (prayer: Prayer) => {
try {
await addDoc(collection(db, 'sharedPrayers'), {
...prayer,
userId: null, // Anonymous
sharedAt: new Date(),
});
loadSharedPrayers(); // Reload
alert('Prayer shared to community!');
} catch (error) {
console.error('Error sharing to community:', error);
}
};
// Archive/unarchive prayers in Firestore
const toggleArchivePrayer = async (prayerId: string, archived: boolean) => {
try {
await updateDoc(doc(db, 'prayers', prayerId), {
archived: archived
});
// Update local state
setPrayers(prev => prev.map(p =>
p.id === prayerId ? { ...p, archived: archived } : p
));
} catch (error) {
console.error('Error updating prayer archive status:', error);
alert('Failed to update prayer. Please try again.');
}
};
// Mark prayer as answered/unanswered
const toggleAnsweredPrayer = async (prayerId: string, answered: boolean) => {
try {
const updateData: any = { answered };
if (answered) {
updateData.answeredDate = new Date().toISOString().split('T')[0];
} else {
updateData.answeredDate = null;
}
await updateDoc(doc(db, 'prayers', prayerId), updateData);
// Update local state
setPrayers(prev => prev.map(p =>
p.id === prayerId ? { ...p, answered, answeredDate: answered ? new Date().toISOString().split('T')[0] : undefined } : p
));
// Send notification if answered and notifications are enabled
if (answered && userProfile.notificationsEnabled) {
// This would trigger a server-side notification via Firebase Functions
console.log('Prayer answered - notification would be sent');
}
} catch (error) {
console.error('Error updating prayer answered status:', error);
alert('Failed to update prayer. Please try again.');
}
};
// Start editing a prayer
const startEditing = (prayer: Prayer) => {
setEditingPrayer(prayer);
setForm({
date: prayer.date,
type: prayer.type,
text: prayer.text,
journal: prayer.journal || '',
email: prayer.email,
prayFor: prayer.prayFor || '',
reminderFrequency: prayer.reminderFrequency || 'never',
includeActiveSummary: prayer.includeActiveSummary || false,
});
setActiveTab('add'); // Switch to add tab to show form
};
// Cancel editing
const cancelEditing = () => {
setEditingPrayer(null);
setForm({
date: new Date().toISOString().split('T')[0],
type: 'prayer',
text: '',
journal: '',
email: user?.email || '',
prayFor: '',
reminderFrequency: 'never',
includeActiveSummary: false,
});
};
if (loading || showLoadingScreen) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-black to-gray-800 flex items-center justify-center p-4">
<div className="text-center">
{/* Animated prayer icon */}
<div className="mb-8 relative">
<div className="w-24 h-24 mx-auto relative">
{/* Outer ring */}
<div className="absolute inset-0 border-4 border-blue-500/30 rounded-full animate-spin"></div>
{/* Inner ring */}
<div className="absolute inset-2 border-4 border-transparent border-t-blue-400 rounded-full animate-spin" style={{animationDirection: 'reverse', animationDuration: '1.5s'}}></div>
{/* Center icon */}
<div className="absolute inset-0 flex items-center justify-center">
<div className="text-3xl animate-pulse">🙏</div>
</div>
</div>
{/* Floating particles */}
<div className="absolute -top-2 -right-2 w-3 h-3 bg-blue-400 rounded-full animate-bounce" style={{animationDelay: '0s'}}></div>
<div className="absolute -bottom-1 -left-3 w-2 h-2 bg-purple-400 rounded-full animate-bounce" style={{animationDelay: '0.5s'}}></div>
<div className="absolute top-1/2 -right-4 w-2 h-2 bg-green-400 rounded-full animate-bounce" style={{animationDelay: '1s'}}></div>
</div>
{/* Loading text */}
<h1 className="text-2xl font-bold text-white mb-2 animate-pulse">
Prayer App
</h1>
<p className="text-gray-400 text-lg mb-4">
Preparing your spiritual journey...
</p>
{/* Inspirational quote */}
<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>
{/* Progress dots */}
<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-black text-white p-4">
<div className="flex justify-between items-center mb-8">
<div>
<h1 className="text-3xl font-bold">Prayer App</h1>
<p className="text-gray-400 text-sm mt-1">
{editingPrayer ? 'Edit Prayer or Praise' :
activeTab === 'add' ? 'Add New Prayer or Praise' :
activeTab === 'search' ? 'Search Results' :
`${activeTab.charAt(0).toUpperCase() + activeTab.slice(1)} Prayers`}
</p>
</div>
<div className="flex gap-4">
<button
onClick={() => setShowProfile(!showProfile)}
className="px-4 py-3 bg-blue-600 hover:bg-blue-700 rounded text-base font-medium"
>
{showProfile ? 'Hide Profile' : 'Profile'}
</button>
{isAdmin && (
<button
onClick={() => setShowAdmin(!showAdmin)}
className="px-4 py-3 bg-purple-600 hover:bg-purple-700 rounded text-base font-medium"
>
{showAdmin ? 'Hide Admin' : 'Admin'}
</button>
)}
<button onClick={() => logout()} className="px-4 py-3 bg-red-600 hover:bg-red-700 rounded text-base font-medium">
Logout
</button>
</div>
</div>
{showAdmin && <Admin />}
{showProfile && <Profile />}
{(activeTab === 'active' || activeTab === 'archived') && (
<div className="max-w-md mx-auto mb-4 flex flex-wrap gap-2 justify-center">
<button
onClick={() => {
const prayerList = activeTab === 'active' ? prayers.filter(p => !p.archived) : prayers.filter(p => p.archived);
exportToPDF(prayerList, `${activeTab} Prayers`);
}}
className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white text-sm font-medium rounded-lg transition-all duration-200"
>
Export PDF
</button>
<CSVLink
data={getCSVData(activeTab === 'active' ? prayers.filter(p => !p.archived) : prayers.filter(p => p.archived))}
filename={`${activeTab}_prayers.csv`}
className="px-4 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg transition-all duration-200 inline-block"
>
Export CSV
</CSVLink>
<button
onClick={() => {
const prayerList = activeTab === 'active' ? prayers.filter(p => !p.archived) : prayers.filter(p => p.archived);
exportToJSON(prayerList, `${activeTab} Prayers`);
}}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg transition-all duration-200"
>
Export JSON
</button>
</div>
)}
{activeTab === 'add' && (
<form onSubmit={handleSubmit} className="max-w-md mx-auto mb-8 space-y-6">
<div>
<label className="block mb-2 text-lg">Date</label>
<input
type="date"
value={form.date}
onChange={e => setForm(prev => ({ ...prev, date: e.target.value }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base"
required
/>
</div>
<div>
<label className="block mb-2 text-lg">Type</label>
<select
value={form.type}
onChange={e => setForm(prev => ({ ...prev, type: e.target.value as 'prayer' | 'praise' }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base"
>
<option value="prayer">Prayer</option>
<option value="praise">Praise</option>
</select>
</div>
<div>
<label className="block mb-2 text-lg">Pray For (Person)</label>
<input
type="text"
value={form.prayFor}
onChange={e => setForm(prev => ({ ...prev, prayFor: e.target.value }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base"
placeholder="Name of person to pray for..."
/>
</div>
<div>
<label className="block mb-2 text-lg">Text</label>
<textarea
value={form.text}
onChange={e => setForm(prev => ({ ...prev, text: e.target.value }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base min-h-[100px]"
placeholder="Enter your prayer or praise..."
required
/>
</div>
<div>
<label className="block mb-2 text-lg">Journal</label>
<textarea
value={form.journal}
onChange={e => setForm(prev => ({ ...prev, journal: e.target.value }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base min-h-[100px]"
placeholder="Optional journal entry..."
/>
</div>
<div>
<label className="block mb-2 text-lg">Email (for reminders)</label>
<input
type="email"
value={form.email}
onChange={e => setForm(prev => ({ ...prev, email: e.target.value }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base"
placeholder="your@email.com"
/>
</div>
<div>
<label className="block mb-2 text-lg">Reminder Frequency</label>
<select
value={form.reminderFrequency}
onChange={e => setForm(prev => ({ ...prev, reminderFrequency: e.target.value as 'daily' | 'weekly' | 'monthly' | 'never' }))}
className="w-full p-3 bg-gray-800 border border-gray-600 rounded text-base"
>
<option value="never">Never</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
<option value="monthly">Monthly</option>
</select>
</div>
<div className="flex items-center mb-4">
<input
type="checkbox"
checked={form.includeActiveSummary}
onChange={e => setForm(prev => ({ ...prev, includeActiveSummary: e.target.checked }))}
className="w-6 h-6 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
/>
<label className="ml-3 text-base font-medium" onClick={() => setForm(prev => ({ ...prev, includeActiveSummary: !prev.includeActiveSummary }))}>
Include active prayers summary in reminders
</label>
</div>
<button type="submit" className="w-full p-4 bg-blue-600 hover:bg-blue-700 rounded text-lg font-semibold mt-6">
{editingPrayer ? 'Update Entry' : 'Add Entry'}
</button>
{editingPrayer && (
<button
type="button"
onClick={cancelEditing}
className="w-full p-4 bg-gray-600 hover:bg-gray-700 rounded text-lg font-semibold mt-2"
>
Cancel Editing
</button>
)}
</form>
)}
{activeTab !== 'add' && (
<div className="max-w-4xl mx-auto">
{activeTab === 'search' && (
<div className="mb-6 space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<input
type="text"
placeholder="Search keywords..."
value={searchTerm}
onChange={(e) => {
setSearchTerm(e.target.value);
setCurrentPage(1);
}}
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<select
value={typeFilter}
onChange={(e) => {
setTypeFilter(e.target.value as 'all' | 'prayer' | 'praise');
setCurrentPage(1);
}}
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="all">All Types</option>
<option value="prayer">Prayers Only</option>
<option value="praise">Praises Only</option>
</select>
<input
type="date"
placeholder="From date"
value={dateFrom}
onChange={(e) => {
setDateFrom(e.target.value);
setCurrentPage(1);
}}
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<input
type="date"
placeholder="To date"
value={dateTo}
onChange={(e) => {
setDateTo(e.target.value);
setCurrentPage(1);
}}
className="p-3 bg-gray-800 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="flex justify-between items-center">
<button
onClick={() => {
setSearchTerm('');
setTypeFilter('all');
setDateFrom('');
setDateTo('');
setCurrentPage(1);
}}
className="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg"
>
Clear Filters
</button>
{(searchTerm || typeFilter !== 'all' || dateFrom || dateTo) && (
<p className="text-gray-400 text-sm">
Found {filteredSearchPrayers.length} prayer{filteredSearchPrayers.length !== 1 ? 's' : ''}
</p>
)}
</div>
</div>
)}
{currentPrayers.length === 0 && (
<div className="text-center py-8">
<p className="text-gray-500">
{activeTab === 'search' && searchTerm ? 'No prayers found matching your search.' : 'No prayers or praises found.'}
</p>
</div>
)}
{Object.keys(groupedPrayers).sort().reverse().map(date => (
<div key={date} className="mb-8">
<h2 className="text-xl font-semibold mb-4">{new Date(date).toLocaleDateString()}</h2>
{groupedPrayers[date].map(prayer => (
<div key={prayer.id} className={`bg-gray-800 p-4 rounded mb-4 ${prayer.archived || activeTab === 'search' ? 'opacity-75 border border-gray-600' : ''}`}>
<div className="flex justify-between items-start mb-3">
<span className="font-medium capitalize text-lg">{prayer.type}</span>
<div className="flex gap-2">
<button
onClick={() => sharePrayer(prayer)}
className="px-3 py-2 bg-blue-600 hover:bg-blue-700 text-white text-sm font-medium rounded-lg flex items-center gap-2 transition-all duration-200 shadow-md hover:shadow-lg"
title="Share this prayer"
>
📤 Share
</button>
<button
onClick={() => shareToCommunity(prayer)}
className="px-3 py-2 bg-green-600 hover:bg-green-700 text-white text-sm font-medium rounded-lg flex items-center gap-2 transition-all duration-200 shadow-md hover:shadow-lg"
title="Share to community"
>
🌍 Community
</button>
<button
onClick={() => startEditing(prayer)}
className="px-3 py-2 bg-yellow-600 hover:bg-yellow-700 text-white text-sm font-medium rounded-lg flex items-center gap-2 transition-all duration-200 shadow-md hover:shadow-lg"
title="Edit this prayer"
>
✏️ Edit
</button>
</div>
</div>
<p className="mb-2">{prayer.text}</p>
{prayer.journal && (
<div className="border-t border-gray-600 pt-2">
<p className="text-sm text-gray-400">Journal: {prayer.journal}</p>
</div>
)}
{prayer.prayFor && (
<div className="border-t border-gray-600 pt-2">
<p className="text-sm text-gray-400">Pray For: {prayer.prayFor}</p>
</div>
)}
{activeTab !== 'search' && (
<div className="flex justify-between items-center mt-3 pt-2 border-t border-gray-600">
<div className="flex items-center gap-2">
{prayer.answered && (
<span className="text-green-400 text-sm font-medium flex items-center gap-1">
✅ Answered {prayer.answeredDate && `on ${new Date(prayer.answeredDate).toLocaleDateString()}`}
</span>
)}
<button
onClick={() => toggleAnsweredPrayer(prayer.id, !prayer.answered)}
className={`px-3 py-1 text-sm font-medium rounded transition-colors ${
prayer.answered
? 'text-yellow-500 hover:text-yellow-400 hover:bg-gray-700'
: 'text-green-500 hover:text-green-400 hover:bg-gray-700'
}`}
title={prayer.answered ? 'Mark as unanswered' : 'Mark as answered'}
>
{prayer.answered ? '↩️ Unmark' : '✅ Mark Answered'}
</button>
</div>
{prayer.archived ? (
<button
onClick={() => toggleArchivePrayer(prayer.id, false)}
className="px-3 py-1 text-green-500 hover:text-green-400 text-sm font-medium hover:bg-gray-700 rounded transition-colors"
title="Restore prayer"
>
↩️ Restore
</button>
) : (
<button
onClick={() => toggleArchivePrayer(prayer.id, true)}
className="px-3 py-1 text-red-500 hover:text-red-400 text-sm font-medium hover:bg-gray-700 rounded transition-colors"
title="Archive prayer"
>
📂 Archive
</button>
)}
</div>
)}
</div>
))}
</div>
))}
{/* Pagination */}
{currentTotalPages > 1 && (
<div className="flex justify-center items-center gap-4 mt-8 mb-6">
<button
onClick={() => setCurrentPage(prev => Math.max(1, prev - 1))}
disabled={currentPage === 1}
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 disabled:cursor-not-allowed rounded text-white"
>
Previous
</button>
<span className="text-gray-300">
Page {currentPage} of {currentTotalPages}
</span>
<button
onClick={() => setCurrentPage(prev => Math.min(currentTotalPages, prev + 1))}
disabled={currentPage === currentTotalPages}
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 disabled:bg-gray-800 disabled:cursor-not-allowed rounded text-white"
>
Next
</button>
</div>
)}
</div>
)}
{/* Bottom Navigation Tabs */}
<div className="fixed bottom-0 left-0 right-0 bg-gradient-to-t from-gray-900 via-gray-900 to-gray-800 border-t border-gray-700 shadow-2xl">
<div className="grid grid-cols-5 px-2 py-2">
<button
onClick={() => {
setActiveTab('add');
setCurrentPage(1);
}}
className={`mx-1 py-3 px-2 rounded-xl text-center transition-all duration-200 text-sm font-semibold flex flex-col items-center justify-center min-h-[60px] ${
activeTab === 'add'
? 'bg-gradient-to-br from-blue-600 to-blue-700 text-white shadow-lg transform scale-105'
: 'text-gray-400 hover:text-white hover:bg-gray-800/50 active:bg-gray-700/50'
}`}
>
<span className="text-lg mb-1">➕</span>
<span className="text-xs leading-tight">Add</span>
</button>
<button
onClick={() => {
setActiveTab('active');
setCurrentPage(1);
setSearchTerm(''); // Clear search when switching to active
}}
className={`mx-1 py-3 px-2 rounded-xl text-center transition-all duration-200 text-sm font-semibold flex flex-col items-center justify-center min-h-[60px] ${
activeTab === 'active'
? 'bg-gradient-to-br from-green-600 to-green-700 text-white shadow-lg transform scale-105'
: 'text-gray-400 hover:text-white hover:bg-gray-800/50 active:bg-gray-700/50'
}`}
>
<span className="text-lg mb-1">📖</span>
<span className="text-xs leading-tight">Active</span>
</button>
<button
onClick={() => {
setActiveTab('archived');
setCurrentPage(1);
setSearchTerm(''); // Clear search when switching to archived
}}
className={`mx-1 py-3 px-2 rounded-xl text-center transition-all duration-200 text-sm font-semibold flex flex-col items-center justify-center min-h-[60px] ${
activeTab === 'archived'
? 'bg-gradient-to-br from-purple-600 to-purple-700 text-white shadow-lg transform scale-105'
: 'text-gray-400 hover:text-white hover:bg-gray-800/50 active:bg-gray-700/50'
}`}
>
<span className="text-lg mb-1">📂</span>
<span className="text-xs leading-tight">Archived</span>
</button>
<button
onClick={() => {
setActiveTab('search');
setCurrentPage(1);
}}
className={`mx-1 py-3 px-2 rounded-xl text-center transition-all duration-200 text-sm font-semibold flex flex-col items-center justify-center min-h-[60px] ${
activeTab === 'search'
? 'bg-gradient-to-br from-orange-600 to-orange-700 text-white shadow-lg transform scale-105'
: 'text-gray-400 hover:text-white hover:bg-gray-800/50 active:bg-gray-700/50'
}`}
>
<span className="text-lg mb-1">🔍</span>
<span className="text-xs leading-tight">Search</span>
</button>
<button
onClick={() => {
setActiveTab('community');
setCurrentPage(1);
setSearchTerm(''); // Clear search when switching to community
}}
className={`mx-1 py-3 px-2 rounded-xl text-center transition-all duration-200 text-sm font-semibold flex flex-col items-center justify-center min-h-[60px] ${
activeTab === 'community'
? 'bg-gradient-to-br from-teal-600 to-teal-700 text-white shadow-lg transform scale-105'
: 'text-gray-400 hover:text-white hover:bg-gray-800/50 active:bg-gray-700/50'
}`}
>
<span className="text-lg mb-1">🌍</span>
<span className="text-xs leading-tight">Community</span>
</button>
</div>
</div>
{/* Add padding to account for fixed bottom nav */}
<div className="pb-20"></div>
</div>
);
}