feat: Add prayer chains, social sharing, and testimony wall

- Add PrayerChain component for visualizing linked prayers
- Add SharePrayer component with social media integration (Twitter, Facebook, WhatsApp, copy link)
- Create /testimonies page for public answered prayer stories
- Update Firestore rules for public testimony access
- Enhance Organization tab with prayer chains
- Add Testimonies navigation tab
- Fix Firebase Admin SDK integration for server-side operations
- Improve email system with proper server-side authentication
- Update prayer form to handle optional fields correctly
- Deploy updated Firestore security rules
This commit is contained in:
nmemmert committed 2025-12-17 13:50:21 -05:00
1 parent 7214fdb89a
commit d9784738e4
32 files changed
+3146 -3236

No files matched your search

+30
View File
@@ -0,0 +1,30 @@
'use client';
import { ThemeProvider } from 'next-themes';
import { AuthProvider } from '@/context/AuthContext';
import { Toaster } from 'react-hot-toast';
export default function ClientLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ThemeProvider attribute="class" defaultTheme="dark">
<AuthProvider>
{children}
<Toaster
position="top-right"
toastOptions={{
duration: 4000,
style: {
background: '#1f2937',
color: '#fff',
border: '1px solid #374151',
},
}}
/>
</AuthProvider>
</ThemeProvider>
);
}
+288
View File
@@ -0,0 +1,288 @@
import React, { useState, useEffect } from 'react';
import { db } from '../lib/firebase';
import { collection, addDoc, updateDoc, deleteDoc, doc, getDocs, query, where } from 'firebase/firestore';
import { useAuth } from '../context/AuthContext';
interface EmailTemplate {
id: string;
name: string;
subject: string;
body: string;
type: 'prayer_update' | 'reminder' | 'summary';
createdAt: string;
updatedAt: string;
}
interface EmailTemplatesManagerProps {
onTemplateSelect?: (templateId: string) => void;
selectedTemplateId?: string;
}
export default function EmailTemplatesManager({ onTemplateSelect, selectedTemplateId }: EmailTemplatesManagerProps) {
const { user } = useAuth();
const [templates, setTemplates] = useState<EmailTemplate[]>([]);
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [editingTemplate, setEditingTemplate] = useState<EmailTemplate | null>(null);
const [form, setForm] = useState({
name: '',
subject: '',
body: '',
type: 'prayer_update' as EmailTemplate['type']
});
useEffect(() => {
if (user) {
loadTemplates();
}
}, [user]);
const loadTemplates = async () => {
if (!user) return;
try {
const q = query(collection(db, 'emailTemplates'), where('userId', '==', user.uid));
const querySnapshot = await getDocs(q);
const templatesData = querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
})) as EmailTemplate[];
setTemplates(templatesData);
} catch (error) {
console.error('Error loading email templates:', error);
} finally {
setLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) return;
try {
const templateData = {
...form,
userId: user.uid,
updatedAt: new Date().toISOString()
};
if (editingTemplate) {
await updateDoc(doc(db, 'emailTemplates', editingTemplate.id), templateData);
} else {
await addDoc(collection(db, 'emailTemplates'), {
...templateData,
createdAt: new Date().toISOString()
});
}
setForm({ name: '', subject: '', body: '', type: 'prayer_update' });
setShowForm(false);
setEditingTemplate(null);
loadTemplates();
} catch (error) {
console.error('Error saving email template:', error);
}
};
const handleEdit = (template: EmailTemplate) => {
setEditingTemplate(template);
setForm({
name: template.name,
subject: template.subject,
body: template.body,
type: template.type
});
setShowForm(true);
};
const handleDelete = async (templateId: string) => {
if (!confirm('Are you sure you want to delete this email template?')) return;
try {
await deleteDoc(doc(db, 'emailTemplates', templateId));
loadTemplates();
} catch (error) {
console.error('Error deleting email template:', error);
}
};
const handleCancel = () => {
setShowForm(false);
setEditingTemplate(null);
setForm({ name: '', subject: '', body: '', type: 'prayer_update' });
};
const getTypeLabel = (type: EmailTemplate['type']) => {
switch (type) {
case 'prayer_update': return 'Prayer Update';
case 'reminder': return 'Reminder';
case 'summary': return 'Summary';
default: return type;
}
};
const getTypeColor = (type: EmailTemplate['type']) => {
switch (type) {
case 'prayer_update': return 'bg-blue-600';
case 'reminder': return 'bg-orange-600';
case 'summary': return 'bg-green-600';
default: return 'bg-gray-600';
}
};
if (loading) {
return <div className="text-white">Loading email templates...</div>;
}
return (
<div className="bg-gray-800 rounded-lg p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-white">📧 Email Templates</h2>
<button
onClick={() => setShowForm(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
+ New Template
</button>
</div>
{showForm && (
<form onSubmit={handleSubmit} className="mb-6 p-4 bg-gray-700 rounded-lg">
<h3 className="text-lg font-semibold text-white mb-4">
{editingTemplate ? 'Edit Email Template' : 'Create New Email Template'}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Template Name</label>
<input
type="text"
value={form.name}
onChange={e => setForm(prev => ({ ...prev, name: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., Weekly Prayer Update"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Template Type</label>
<select
value={form.type}
onChange={e => setForm(prev => ({ ...prev, type: e.target.value as EmailTemplate['type'] }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
<option value="prayer_update">Prayer Update</option>
<option value="reminder">Reminder</option>
<option value="summary">Summary</option>
</select>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-300 mb-2">Email Subject</label>
<input
type="text"
value={form.subject}
onChange={e => setForm(prev => ({ ...prev, subject: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., Your Weekly Prayer Update"
required
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-300 mb-2">Email Body</label>
<textarea
value={form.body}
onChange={e => setForm(prev => ({ ...prev, body: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[150px] resize-none"
placeholder={`Write your email template here. Available placeholders:
{userName} - User's name
{prayerText} - The prayer text
{prayFor} - Who they're praying for
{date} - Date of prayer
{type} - Prayer or praise`}
required
/>
</div>
<div className="text-sm text-gray-400 mb-4">
<strong>Available placeholders:</strong> {'{userName}'}, {'{prayerText}'}, {'{prayFor}'}, {'{date}'}, {'{type}'}
</div>
<div className="flex space-x-3">
<button
type="submit"
className="px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
>
{editingTemplate ? 'Update' : 'Create'} Template
</button>
<button
type="button"
onClick={handleCancel}
className="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
>
Cancel
</button>
</div>
</form>
)}
<div className="space-y-4">
{templates.length === 0 ? (
<div className="text-center py-8 text-gray-400">
No email templates created yet. Create your first template to get started!
</div>
) : (
templates.map(template => (
<div key={template.id} className="p-4 bg-gray-700 rounded-lg">
<div className="flex justify-between items-start mb-3">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<h3 className="text-lg font-semibold text-white">{template.name}</h3>
<span className={`px-2 py-1 text-white text-xs rounded-full ${getTypeColor(template.type)}`}>
{getTypeLabel(template.type)}
</span>
</div>
<p className="text-gray-300 text-sm mb-2">
<strong>Subject:</strong> {template.subject}
</p>
</div>
<div className="flex space-x-2">
{onTemplateSelect && (
<button
onClick={() => onTemplateSelect(template.id)}
className={`px-3 py-1 rounded text-xs transition-colors ${
selectedTemplateId === template.id
? 'bg-green-600 text-white'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
>
{selectedTemplateId === template.id ? 'Selected' : 'Select'}
</button>
)}
<button
onClick={() => handleEdit(template)}
className="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white rounded text-xs transition-colors"
>
Edit
</button>
<button
onClick={() => handleDelete(template.id)}
className="px-3 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-xs transition-colors"
>
Delete
</button>
</div>
</div>
<div className="bg-gray-600 p-3 rounded text-gray-200 text-sm whitespace-pre-wrap max-h-32 overflow-y-auto">
{template.body}
</div>
</div>
))
)}
</div>
</div>
);
}
+9
View File
@@ -0,0 +1,9 @@
export default function LoadingSkeleton() {
return (
<div className="animate-pulse space-y-4">
<div className="h-4 bg-gray-700 rounded w-3/4"></div>
<div className="h-4 bg-gray-700 rounded w-1/2"></div>
<div className="h-4 bg-gray-700 rounded w-5/6"></div>
</div>
);
}
+82 -48
View File
@@ -9,15 +9,18 @@ export default function Login() {
const [confirmPassword, setConfirmPassword] = useState('');
const [isSignup, setIsSignup] = useState(false);
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const { login, signup } = useAuth();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError('');
setLoading(true);
if (isSignup) {
if (password !== confirmPassword) {
setError('Passwords do not match');
setLoading(false);
return;
}
try {
@@ -32,62 +35,93 @@ export default function Login() {
setError(err instanceof Error ? err.message : 'An error occurred');
}
}
setLoading(false);
};
return (
<div className="min-h-screen bg-black text-white flex items-center justify-center">
<form onSubmit={handleSubmit} className="bg-gray-800 p-8 rounded-lg w-full max-w-md">
<h1 className="text-2xl font-bold mb-6 text-center">
{isSignup ? 'Sign Up for Prayer App' : 'Login to Prayer App'}
</h1>
{error && <p className="text-red-500 mb-4">{error}</p>}
<div className="mb-4">
<label className="block mb-2">Email</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 bg-gray-700 border border-gray-600 rounded"
required
/>
<div className="min-h-screen bg-gradient-to-br from-gray-900 to-black text-white flex items-center justify-center p-4">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold mb-2">Prayer App</h1>
<p className="text-gray-400">Manage your spiritual journey</p>
</div>
<div className="mb-4">
<label className="block mb-2">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-2 bg-gray-700 border border-gray-600 rounded"
required
/>
</div>
{isSignup && (
<div className="mb-6">
<label className="block mb-2">Confirm Password</label>
<form onSubmit={handleSubmit} className="bg-gray-800 p-8 rounded-xl shadow-2xl space-y-6">
<h2 className="text-2xl font-semibold text-center">
{isSignup ? 'Create Account' : 'Welcome Back'}
</h2>
{error && (
<div className="bg-red-900 border border-red-700 text-red-200 px-4 py-3 rounded-lg">
{error}
</div>
)}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Email</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full p-2 bg-gray-700 border border-gray-600 rounded"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
autoFocus
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Enter your email"
required
/>
</div>
)}
<button type="submit" className="w-full p-2 bg-blue-600 hover:bg-blue-700 rounded mb-4">
{isSignup ? 'Sign Up' : 'Login'}
</button>
<button
type="button"
onClick={() => {
setIsSignup(!isSignup);
setError('');
setConfirmPassword('');
}}
className="w-full p-2 bg-gray-600 hover:bg-gray-700 rounded"
>
{isSignup ? 'Already have an account? Login' : 'Need an account? Sign Up'}
</button>
</form>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Password</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Enter your password"
required
/>
</div>
{isSignup && (
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Confirm Password</label>
<input
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
placeholder="Confirm your password"
required
/>
</div>
)}
<button
type="submit"
disabled={loading}
className="w-full p-3 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-800 disabled:cursor-not-allowed rounded-lg font-medium transition-colors flex items-center justify-center"
>
{loading ? (
<>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing...
</>
) : (
isSignup ? 'Create Account' : 'Sign In'
)}
</button>
<div className="text-center">
<button
type="button"
onClick={() => {
setIsSignup(!isSignup);
setError('');
setConfirmPassword('');
}}
className="text-blue-400 hover:text-blue-300 transition-colors"
>
{isSignup ? 'Already have an account? Sign In' : 'Need an account? Sign Up'}
</button>
</div>
</form>
</div>
</div>
);
}
+207
View File
@@ -0,0 +1,207 @@
'use client';
import { useState } from 'react';
import { useAuth } from '@/context/AuthContext';
import { useRouter } from 'next/navigation';
interface NavbarProps {
activeTab?: string;
onTabChange?: (tab: string) => void;
onSearchClear?: () => void;
onPageReset?: () => void;
}
export default function Navbar({ activeTab, onTabChange, onSearchClear, onPageReset }: NavbarProps) {
const { user, logout, isAdmin } = useAuth();
const router = useRouter();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
if (!user) return null;
const navigationTabs = [
{ key: 'dashboard', label: 'Dashboard', icon: '📊' },
{ key: 'add', label: 'Add Prayer', icon: '➕' },
{ key: 'active', label: 'Active', icon: '📖' },
{ key: 'archived', label: 'Archived', icon: '📂' },
{ key: 'search', label: 'Search', icon: '🔍' },
{ key: 'community', label: 'Community', icon: '🌍' },
{ key: 'organization', label: 'Organization', icon: '🗂️' },
{ key: 'testimonies', label: 'Testimonies', icon: '🙌' },
];
const handleTabChange = (tabKey: string) => {
if (tabKey === 'testimonies') {
router.push('/testimonies');
setIsMobileMenuOpen(false);
return;
}
if (onTabChange) {
onTabChange(tabKey);
}
if (tabKey !== 'search' && onSearchClear) {
onSearchClear();
}
if (onPageReset) {
onPageReset();
}
setIsMobileMenuOpen(false);
};
return (
<nav className="bg-gray-900/95 backdrop-blur-sm border-b border-gray-800/50 px-4 py-3 shadow-lg">
<div className="max-w-6xl mx-auto flex items-center justify-between">
{/* Logo/Brand */}
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">🙏</span>
</div>
<button
onClick={() => {
handleTabChange('dashboard');
router.push('/');
}}
className="text-lg md:text-xl font-bold bg-gradient-to-r from-blue-400 to-purple-500 bg-clip-text text-transparent hover:from-blue-300 hover:to-purple-400 transition-colors"
>
Prayer App
</button>
</div>
{/* Desktop Navigation - Hidden on mobile */}
<div className="hidden lg:flex items-center space-x-2">
{navigationTabs.map(({ key, label, icon }) => (
<button
key={key}
onClick={() => handleTabChange(key)}
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 flex items-center space-x-2 text-sm ${
activeTab === key
? 'bg-blue-600 text-white shadow-lg'
: 'text-gray-300 hover:text-white hover:bg-gray-800/50'
}`}
>
<span>{icon}</span>
<span>{label}</span>
</button>
))}
{isAdmin && (
<button
onClick={() => router.push('/admin')}
className="text-gray-300 hover:text-white transition-colors px-3 py-2 rounded-lg hover:bg-gray-800/50 text-sm"
>
Admin
</button>
)}
<button
onClick={() => router.push('/profile')}
className="text-gray-300 hover:text-white transition-colors px-3 py-2 rounded-lg hover:bg-gray-800/50 text-sm"
>
Profile
</button>
<button
onClick={logout}
className="bg-red-600/20 hover:bg-red-600/30 text-red-400 hover:text-red-300 px-4 py-2 rounded-lg transition-all duration-200 border border-red-500/30 text-sm"
>
Logout
</button>
</div>
{/* Mobile Menu Button */}
<div className="lg:hidden">
<button
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
className="text-gray-300 hover:text-white p-2 rounded-lg hover:bg-gray-800/50 transition-colors"
aria-label="Toggle mobile menu"
>
<svg
className="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
{isMobileMenuOpen ? (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
) : (
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 6h16M4 12h16M4 18h16"
/>
)}
</svg>
</button>
</div>
</div>
{/* Mobile Menu */}
{isMobileMenuOpen && (
<div className="lg:hidden border-t border-gray-800/50 mt-3 pt-3">
<div className="flex flex-col space-y-1">
{/* Main Navigation Tabs */}
{navigationTabs.map(({ key, label, icon }) => (
<button
key={key}
onClick={() => handleTabChange(key)}
className={`w-full text-left px-4 py-3 rounded-lg transition-colors flex items-center space-x-3 ${
activeTab === key
? 'bg-blue-600 text-white'
: 'text-gray-300 hover:text-white hover:bg-gray-800/50'
}`}
>
<span className="text-lg">{icon}</span>
<span className="font-medium">{label}</span>
{activeTab === key && (
<span className="ml-auto text-xs bg-blue-500/20 text-blue-300 px-2 py-1 rounded-full">
Active
</span>
)}
</button>
))}
{/* Separator */}
<div className="border-t border-gray-700 my-2"></div>
{/* Additional Options */}
{isAdmin && (
<button
onClick={() => {
router.push('/admin');
setIsMobileMenuOpen(false);
}}
className="w-full text-left text-gray-300 hover:text-white transition-colors px-4 py-3 rounded-lg hover:bg-gray-800/50 flex items-center space-x-3"
>
<span className="text-lg">⚙️</span>
<span>Admin Panel</span>
</button>
)}
<button
onClick={() => {
router.push('/profile');
setIsMobileMenuOpen(false);
}}
className="w-full text-left text-gray-300 hover:text-white transition-colors px-4 py-3 rounded-lg hover:bg-gray-800/50 flex items-center space-x-3"
>
<span className="text-lg">👤</span>
<span>Profile</span>
</button>
<button
onClick={() => {
logout();
setIsMobileMenuOpen(false);
}}
className="w-full text-left bg-red-600/20 hover:bg-red-600/30 text-red-400 hover:text-red-300 px-4 py-3 rounded-lg transition-all duration-200 border border-red-500/30 flex items-center space-x-3"
>
<span className="text-lg">🚪</span>
<span>Logout</span>
</button>
</div>
</div>
)}
</nav>
);
}
+54
View File
@@ -0,0 +1,54 @@
import React from 'react';
interface Prayer {
id: string;
text: string;
type: 'prayer' | 'praise';
answered?: boolean;
chainId?: string;
chainOrder?: number;
}
interface PrayerChainProps {
prayers: Prayer[];
onChainClick: (chainId: string) => void;
}
export default function PrayerChain({ prayers, onChainClick }: PrayerChainProps) {
const chains = prayers.reduce((acc, prayer) => {
if (prayer.chainId) {
if (!acc[prayer.chainId]) acc[prayer.chainId] = [];
acc[prayer.chainId].push(prayer);
}
return acc;
}, {} as Record<string, Prayer[]>);
return (
<div className="space-y-4">
{Object.entries(chains).map(([chainId, chainPrayers]) => (
<div key={chainId} className="bg-gray-800/50 rounded-lg p-4 border border-gray-700">
<h3 className="text-lg font-semibold mb-2 text-white">Prayer Chain</h3>
<div className="flex flex-col space-y-2">
{chainPrayers
.sort((a, b) => (a.chainOrder || 0) - (b.chainOrder || 0))
.map((prayer, index) => (
<div key={prayer.id} className="flex items-center space-x-2">
<div className="w-4 h-4 bg-blue-500 rounded-full flex items-center justify-center text-xs text-white font-bold">
{index + 1}
</div>
<div className="flex-1 text-sm text-gray-200 truncate">{prayer.text}</div>
{prayer.answered && <span className="text-green-400">✓</span>}
</div>
))}
</div>
<button
onClick={() => onChainClick(chainId)}
className="mt-2 text-blue-400 hover:text-blue-300 text-sm underline"
>
View Full Chain
</button>
</div>
))}
</div>
);
}
+316
View File
@@ -0,0 +1,316 @@
import React, { useState, useEffect } from 'react';
import { db } from '../lib/firebase';
import { collection, addDoc, updateDoc, deleteDoc, doc, getDocs, query, where } from 'firebase/firestore';
import { useAuth } from '../context/AuthContext';
interface PrayerGoal {
id: string;
title: string;
description: string;
targetCount: number;
currentCount: number;
category: string;
startDate: string;
endDate: string;
isCompleted: boolean;
createdAt: string;
updatedAt: string;
}
interface PrayerGoalsManagerProps {
onGoalSelect?: (goalId: string) => void;
selectedGoalId?: string;
}
export default function PrayerGoalsManager({ onGoalSelect, selectedGoalId }: PrayerGoalsManagerProps) {
const { user } = useAuth();
const [goals, setGoals] = useState<PrayerGoal[]>([]);
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [editingGoal, setEditingGoal] = useState<PrayerGoal | null>(null);
const [form, setForm] = useState({
title: '',
description: '',
targetCount: 1,
category: '',
startDate: '',
endDate: ''
});
useEffect(() => {
if (user) {
loadGoals();
}
}, [user]);
const loadGoals = async () => {
if (!user) return;
try {
const q = query(collection(db, 'prayerGoals'), where('userId', '==', user.uid));
const querySnapshot = await getDocs(q);
const goalsData = querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
})) as PrayerGoal[];
setGoals(goalsData);
} catch (error) {
console.error('Error loading goals:', error);
} finally {
setLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) return;
try {
const goalData = {
...form,
currentCount: editingGoal ? editingGoal.currentCount : 0,
isCompleted: false,
userId: user.uid,
updatedAt: new Date().toISOString()
};
if (editingGoal) {
await updateDoc(doc(db, 'prayerGoals', editingGoal.id), goalData);
} else {
await addDoc(collection(db, 'prayerGoals'), {
...goalData,
createdAt: new Date().toISOString()
});
}
setForm({ title: '', description: '', targetCount: 1, category: '', startDate: '', endDate: '' });
setShowForm(false);
setEditingGoal(null);
loadGoals();
} catch (error) {
console.error('Error saving goal:', error);
}
};
const handleEdit = (goal: PrayerGoal) => {
setEditingGoal(goal);
setForm({
title: goal.title,
description: goal.description,
targetCount: goal.targetCount,
category: goal.category,
startDate: goal.startDate,
endDate: goal.endDate
});
setShowForm(true);
};
const handleDelete = async (goalId: string) => {
if (!confirm('Are you sure you want to delete this goal?')) return;
try {
await deleteDoc(doc(db, 'prayerGoals', goalId));
loadGoals();
} catch (error) {
console.error('Error deleting goal:', error);
}
};
const handleCancel = () => {
setShowForm(false);
setEditingGoal(null);
setForm({ title: '', description: '', targetCount: 1, category: '', startDate: '', endDate: '' });
};
const getProgressPercentage = (current: number, target: number) => {
return Math.min((current / target) * 100, 100);
};
if (loading) {
return <div className="text-white">Loading goals...</div>;
}
return (
<div className="bg-gray-800 rounded-lg p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-white">🎯 Prayer Goals</h2>
<button
onClick={() => setShowForm(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
+ New Goal
</button>
</div>
{showForm && (
<form onSubmit={handleSubmit} className="mb-6 p-4 bg-gray-700 rounded-lg">
<h3 className="text-lg font-semibold text-white mb-4">
{editingGoal ? 'Edit Goal' : 'Create New Goal'}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Goal Title</label>
<input
type="text"
value={form.title}
onChange={e => setForm(prev => ({ ...prev, title: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., Pray for 30 days"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Target Count</label>
<input
type="number"
min="1"
value={form.targetCount}
onChange={e => setForm(prev => ({ ...prev, targetCount: parseInt(e.target.value) || 1 }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., 30"
required
/>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-300 mb-2">Category</label>
<input
type="text"
value={form.category}
onChange={e => setForm(prev => ({ ...prev, category: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., Personal, Family, Ministry"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-300 mb-2">Description</label>
<textarea
value={form.description}
onChange={e => setForm(prev => ({ ...prev, description: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[80px] resize-none"
placeholder="Describe your prayer goal..."
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Start Date</label>
<input
type="date"
value={form.startDate}
onChange={e => setForm(prev => ({ ...prev, startDate: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">End Date (Optional)</label>
<input
type="date"
value={form.endDate}
onChange={e => setForm(prev => ({ ...prev, endDate: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
</div>
<div className="flex space-x-3">
<button
type="submit"
className="px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
>
{editingGoal ? 'Update' : 'Create'} Goal
</button>
<button
type="button"
onClick={handleCancel}
className="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
>
Cancel
</button>
</div>
</form>
)}
<div className="space-y-4">
{goals.length === 0 ? (
<div className="text-center py-8 text-gray-400">
No goals created yet. Create your first prayer goal to get started!
</div>
) : (
goals.map(goal => {
const progressPercent = getProgressPercentage(goal.currentCount, goal.targetCount);
return (
<div key={goal.id} className="p-4 bg-gray-700 rounded-lg">
<div className="flex justify-between items-start mb-3">
<div className="flex-1">
<div className="flex items-center space-x-2 mb-2">
<h3 className="text-lg font-semibold text-white">{goal.title}</h3>
{goal.isCompleted && (
<span className="px-2 py-1 bg-green-600 text-white text-xs rounded-full">
Completed
</span>
)}
{goal.category && (
<span className="px-2 py-1 bg-blue-600 text-white text-xs rounded-full">
{goal.category}
</span>
)}
</div>
{goal.description && (
<p className="text-gray-300 text-sm mb-3">{goal.description}</p>
)}
<div className="flex items-center space-x-4 text-sm text-gray-400">
<span>Progress: {goal.currentCount} / {goal.targetCount}</span>
{goal.startDate && <span>Started: {new Date(goal.startDate).toLocaleDateString()}</span>}
{goal.endDate && <span>Ends: {new Date(goal.endDate).toLocaleDateString()}</span>}
</div>
</div>
<div className="flex space-x-2">
{onGoalSelect && (
<button
onClick={() => onGoalSelect(goal.id)}
className={`px-3 py-1 rounded text-xs transition-colors ${
selectedGoalId === goal.id
? 'bg-green-600 text-white'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
>
{selectedGoalId === goal.id ? 'Selected' : 'Select'}
</button>
)}
<button
onClick={() => handleEdit(goal)}
className="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white rounded text-xs transition-colors"
>
Edit
</button>
<button
onClick={() => handleDelete(goal.id)}
className="px-3 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-xs transition-colors"
>
Delete
</button>
</div>
</div>
<div className="w-full bg-gray-600 rounded-full h-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all duration-300"
style={{ width: `${progressPercent}%` }}
></div>
</div>
<div className="text-right text-xs text-gray-400 mt-1">
{Math.round(progressPercent)}% Complete
</div>
</div>
);
})
)}
</div>
</div>
);
}
+258
View File
@@ -0,0 +1,258 @@
import React, { useState, useEffect } from 'react';
import { db } from '../lib/firebase';
import { collection, addDoc, updateDoc, deleteDoc, doc, getDocs, query, where } from 'firebase/firestore';
import { useAuth } from '../context/AuthContext';
interface PrayerTemplate {
id: string;
name: string;
description: string;
templateText: string;
category: string;
createdAt: string;
updatedAt: string;
}
interface PrayerTemplatesManagerProps {
onTemplateSelect?: (templateId: string) => void;
selectedTemplateId?: string;
}
export default function PrayerTemplatesManager({ onTemplateSelect, selectedTemplateId }: PrayerTemplatesManagerProps) {
const { user } = useAuth();
const [templates, setTemplates] = useState<PrayerTemplate[]>([]);
const [loading, setLoading] = useState(true);
const [showForm, setShowForm] = useState(false);
const [editingTemplate, setEditingTemplate] = useState<PrayerTemplate | null>(null);
const [form, setForm] = useState({
name: '',
description: '',
templateText: '',
category: ''
});
useEffect(() => {
if (user) {
loadTemplates();
}
}, [user]);
const loadTemplates = async () => {
if (!user) return;
try {
const q = query(collection(db, 'prayerTemplates'), where('userId', '==', user.uid));
const querySnapshot = await getDocs(q);
const templatesData = querySnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
})) as PrayerTemplate[];
setTemplates(templatesData);
} catch (error) {
console.error('Error loading templates:', error);
} finally {
setLoading(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!user) return;
try {
const templateData = {
...form,
userId: user.uid,
updatedAt: new Date().toISOString()
};
if (editingTemplate) {
await updateDoc(doc(db, 'prayerTemplates', editingTemplate.id), templateData);
} else {
await addDoc(collection(db, 'prayerTemplates'), {
...templateData,
createdAt: new Date().toISOString()
});
}
setForm({ name: '', description: '', templateText: '', category: '' });
setShowForm(false);
setEditingTemplate(null);
loadTemplates();
} catch (error) {
console.error('Error saving template:', error);
}
};
const handleEdit = (template: PrayerTemplate) => {
setEditingTemplate(template);
setForm({
name: template.name,
description: template.description,
templateText: template.templateText,
category: template.category
});
setShowForm(true);
};
const handleDelete = async (templateId: string) => {
if (!confirm('Are you sure you want to delete this template?')) return;
try {
await deleteDoc(doc(db, 'prayerTemplates', templateId));
loadTemplates();
} catch (error) {
console.error('Error deleting template:', error);
}
};
const handleCancel = () => {
setShowForm(false);
setEditingTemplate(null);
setForm({ name: '', description: '', templateText: '', category: '' });
};
if (loading) {
return <div className="text-white">Loading templates...</div>;
}
return (
<div className="bg-gray-800 rounded-lg p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold text-white">📋 Prayer Templates</h2>
<button
onClick={() => setShowForm(true)}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors"
>
+ New Template
</button>
</div>
{showForm && (
<form onSubmit={handleSubmit} className="mb-6 p-4 bg-gray-700 rounded-lg">
<h3 className="text-lg font-semibold text-white mb-4">
{editingTemplate ? 'Edit Template' : 'Create New Template'}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Template Name</label>
<input
type="text"
value={form.name}
onChange={e => setForm(prev => ({ ...prev, name: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., Healing Prayer"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Category</label>
<input
type="text"
value={form.category}
onChange={e => setForm(prev => ({ ...prev, category: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., Health, Family, Work"
/>
</div>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-300 mb-2">Description</label>
<input
type="text"
value={form.description}
onChange={e => setForm(prev => ({ ...prev, description: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Brief description of this template"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-300 mb-2">Template Text</label>
<textarea
value={form.templateText}
onChange={e => setForm(prev => ({ ...prev, templateText: e.target.value }))}
className="w-full p-3 bg-gray-600 border border-gray-500 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent min-h-[120px] resize-none"
placeholder="Write your prayer template here. Use {name} for placeholders..."
required
/>
</div>
<div className="flex space-x-3">
<button
type="submit"
className="px-6 py-2 bg-green-600 hover:bg-green-700 text-white rounded-lg transition-colors"
>
{editingTemplate ? 'Update' : 'Create'} Template
</button>
<button
type="button"
onClick={handleCancel}
className="px-6 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors"
>
Cancel
</button>
</div>
</form>
)}
<div className="space-y-4">
{templates.length === 0 ? (
<div className="text-center py-8 text-gray-400">
No templates created yet. Create your first template to get started!
</div>
) : (
templates.map(template => (
<div key={template.id} className="p-4 bg-gray-700 rounded-lg">
<div className="flex justify-between items-start mb-3">
<div className="flex-1">
<h3 className="text-lg font-semibold text-white">{template.name}</h3>
{template.category && (
<span className="inline-block px-2 py-1 bg-blue-600 text-white text-xs rounded-full mt-1">
{template.category}
</span>
)}
{template.description && (
<p className="text-gray-300 text-sm mt-2">{template.description}</p>
)}
</div>
<div className="flex space-x-2">
{onTemplateSelect && (
<button
onClick={() => onTemplateSelect(template.id)}
className={`px-3 py-1 rounded text-xs transition-colors ${
selectedTemplateId === template.id
? 'bg-green-600 text-white'
: 'bg-blue-600 hover:bg-blue-700 text-white'
}`}
>
{selectedTemplateId === template.id ? 'Selected' : 'Select'}
</button>
)}
<button
onClick={() => handleEdit(template)}
className="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 text-white rounded text-xs transition-colors"
>
Edit
</button>
<button
onClick={() => handleDelete(template.id)}
className="px-3 py-1 bg-red-600 hover:bg-red-700 text-white rounded text-xs transition-colors"
>
Delete
</button>
</div>
</div>
<div className="bg-gray-600 p-3 rounded text-gray-200 text-sm whitespace-pre-wrap">
{template.templateText}
</div>
</div>
))
)}
</div>
</div>
);
}
+92 -83
View File
@@ -31,7 +31,7 @@ export default function Profile() {
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
setProfile(docSnap.data() as UserProfile);
setNotificationsEnabled(docSnap.data().notificationsEnabled);
setNotificationsEnabled(docSnap.data().notificationsEnabled ?? false);
}
} catch (error) {
console.error('Error loading profile:', error);
@@ -44,13 +44,6 @@ export default function Profile() {
}
}, [user, loadProfile]);
// Load notification settings
useEffect(() => {
if (profile.notificationsEnabled !== undefined) {
setNotificationsEnabled(profile.notificationsEnabled);
}
}, [profile.notificationsEnabled]);
// Listen for FCM messages
useEffect(() => {
if (notificationsEnabled) {
@@ -146,88 +139,104 @@ export default function Profile() {
};
return (
<div className="max-w-md mx-auto mb-8 p-6 bg-gray-800 rounded-lg">
<h2 className="text-2xl font-bold mb-6">Profile</h2>
{/* Avatar */}
<div className="mb-6">
<label className="block mb-2 text-lg">Avatar</label>
{profile.avatarUrl && (
<Image src={profile.avatarUrl} alt="Avatar" width={80} height={80} className="w-20 h-20 rounded-full mb-2" />
)}
<input
type="file"
accept="image/*"
onChange={handleAvatarUpload}
disabled={uploading}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded"
/>
{uploading && <p className="text-sm text-gray-400 mt-1">Uploading...</p>}
<div className="max-w-2xl mx-auto p-6 space-y-8">
<div className="text-center">
<h1 className="text-3xl font-bold text-white mb-2">Profile Settings</h1>
<p className="text-gray-400">Manage your account and preferences</p>
</div>
{/* Theme */}
<div className="mb-6">
<label className="block mb-2 text-lg">Theme</label>
<select
value={theme}
onChange={(e) => handleThemeChange(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded"
>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="system">System</option>
</select>
</div>
{/* Notifications */}
<div className="mb-6">
<label className="block mb-2 text-lg">Notifications</label>
<div className="flex items-center mb-4">
<input
type="checkbox"
checked={notificationsEnabled}
onChange={(e) => handleNotificationToggle(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">
Enable push notifications for prayer reminders
</label>
{/* Avatar Section */}
<div className="bg-gray-800 p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4 text-white">Profile Picture</h2>
<div className="flex items-center space-x-4">
{profile.avatarUrl ? (
<Image src={profile.avatarUrl} alt="Avatar" width={80} height={80} className="w-20 h-20 rounded-full border-2 border-gray-600" />
) : (
<div className="w-20 h-20 rounded-full bg-gray-600 flex items-center justify-center text-gray-400">No Image</div>
)}
<div className="flex-1">
<input
type="file"
accept="image/*"
onChange={handleAvatarUpload}
disabled={uploading}
className="block w-full text-sm text-gray-300 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-blue-600 file:text-white hover:file:bg-blue-700 disabled:opacity-50"
/>
{uploading && <p className="text-sm text-blue-400 mt-2">Uploading...</p>}
</div>
</div>
<p className="text-sm text-gray-400">
Get notified about your prayer schedule and when prayers are answered.
</p>
</div>
{/* Categories */}
<div className="mb-6">
<label className="block mb-2 text-lg">Prayer Categories</label>
<div className="flex gap-2 mb-2">
<input
type="text"
value={newCategory}
onChange={(e) => setNewCategory(e.target.value)}
placeholder="Add category"
className="flex-1 p-3 bg-gray-700 border border-gray-600 rounded"
/>
<button
onClick={addCategory}
className="px-4 py-3 bg-green-600 hover:bg-green-700 rounded"
{/* Theme Section */}
<div className="bg-gray-800 p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4 text-white">Appearance</h2>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">Theme</label>
<select
value={theme}
onChange={(e) => handleThemeChange(e.target.value)}
className="w-full p-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
>
Add
</button>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="system">System</option>
</select>
</div>
<div className="flex flex-wrap gap-2">
{(profile.categories || []).map(category => (
<span key={category} className="bg-gray-700 px-3 py-1 rounded flex items-center gap-2">
{category}
<button
onClick={() => removeCategory(category)}
className="text-red-400 hover:text-red-300"
>
×
</button>
</span>
))}
</div>
{/* Notifications Section */}
<div className="bg-gray-800 p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4 text-white">Notifications</h2>
<div className="space-y-3">
<div className="flex items-center">
<input
type="checkbox"
checked={notificationsEnabled}
onChange={(e) => handleNotificationToggle(e.target.checked)}
className="w-5 h-5 text-blue-600 bg-gray-700 border-gray-600 rounded focus:ring-blue-500 focus:ring-2"
/>
<label className="ml-3 text-sm font-medium text-gray-300">
Enable push notifications for prayer reminders
</label>
</div>
<p className="text-sm text-gray-400">
Get notified about your prayer schedule and when prayers are answered.
</p>
</div>
</div>
{/* Categories Section */}
<div className="bg-gray-800 p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-semibold mb-4 text-white">Prayer Categories</h2>
<div className="space-y-4">
<div className="flex space-x-2">
<input
type="text"
value={newCategory}
onChange={(e) => setNewCategory(e.target.value)}
placeholder="Add a new category"
className="flex-1 p-3 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<button
onClick={addCategory}
className="px-6 py-3 bg-green-600 hover:bg-green-700 text-white rounded-lg font-medium transition-colors"
>
Add
</button>
</div>
<div className="flex flex-wrap gap-2">
{(profile.categories || []).map(category => (
<span key={category} className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gray-700 text-gray-300">
{category}
<button
onClick={() => removeCategory(category)}
className="ml-2 text-red-400 hover:text-red-300 transition-colors"
>
×
</button>
</span>
))}
</div>
</div>
</div>
</div>
+74
View File
@@ -0,0 +1,74 @@
import React from 'react';
interface Prayer {
id: string;
text: string;
type: 'prayer' | 'praise';
}
interface SharePrayerProps {
prayer: Prayer;
}
export default function SharePrayer({ prayer }: SharePrayerProps) {
const shareText = `🙏 ${prayer.type === 'prayer' ? 'Prayer' : 'Praise'}: ${prayer.text.substring(0, 100)}${prayer.text.length > 100 ? '...' : ''}`;
const shareUrl = window.location.href;
const shareToTwitter = () => {
const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}&url=${encodeURIComponent(shareUrl)}`;
window.open(url, '_blank', 'width=600,height=400');
};
const shareToFacebook = () => {
const url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}&quote=${encodeURIComponent(shareText)}`;
window.open(url, '_blank', 'width=600,height=400');
};
const shareToWhatsApp = () => {
const url = `https://wa.me/?text=${encodeURIComponent(shareText + ' ' + shareUrl)}`;
window.open(url, '_blank', 'width=600,height=400');
};
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(`${shareText} ${shareUrl}`);
alert('Prayer copied to clipboard!');
} catch (err) {
console.error('Failed to copy: ', err);
}
};
return (
<div className="flex items-center space-x-2 mt-2">
<span className="text-xs text-gray-400">Share:</span>
<button
onClick={shareToTwitter}
className="text-blue-400 hover:text-blue-300 text-sm px-2 py-1 rounded hover:bg-blue-400/10 transition-colors"
title="Share on Twitter"
>
🐦
</button>
<button
onClick={shareToFacebook}
className="text-blue-600 hover:text-blue-500 text-sm px-2 py-1 rounded hover:bg-blue-600/10 transition-colors"
title="Share on Facebook"
>
📘
</button>
<button
onClick={shareToWhatsApp}
className="text-green-400 hover:text-green-300 text-sm px-2 py-1 rounded hover:bg-green-400/10 transition-colors"
title="Share on WhatsApp"
>
💬
</button>
<button
onClick={copyToClipboard}
className="text-gray-400 hover:text-gray-300 text-sm px-2 py-1 rounded hover:bg-gray-400/10 transition-colors"
title="Copy to clipboard"
>
📋
</button>
</div>
);
}