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

+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>