1029 lines
40 KiB
TypeScript
1029 lines
40 KiB
TypeScript
'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>
|
||
);
|
||
}
|