feat: add new simplified components for daily prayer journal
- DailyEntry.tsx: Main prayer input form with clean interface - CalendarView.tsx: Monthly calendar showing entry indicators - ReflectionsList.tsx: Browse and search past prayers - SettingsPage.tsx: User preferences and account settings - BottomNav.tsx: Mobile-first navigation with desktop support - All components use Slate color scheme for intimate aesthetic
This commit is contained in:
1 parent
b032d14673
commit
a5390e22be
5 files changed
+612
No files matched your search
@@ -0,0 +1,76 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
|
||||||
|
interface BottomNavProps {
|
||||||
|
activeTab: 'home' | 'journal' | 'reflections' | 'settings';
|
||||||
|
onTabChange: (tab: 'home' | 'journal' | 'reflections' | 'settings') => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BottomNav({ activeTab, onTabChange }: BottomNavProps) {
|
||||||
|
const { logout, user } = useAuth();
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ id: 'home' as const, label: 'Home', icon: '🏠' },
|
||||||
|
{ id: 'journal' as const, label: 'Journal', icon: '📅' },
|
||||||
|
{ id: 'reflections' as const, label: 'Reflections', icon: '📖' },
|
||||||
|
{ id: 'settings' as const, label: 'Settings', icon: '⚙️' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* Mobile Bottom Navigation */}
|
||||||
|
<nav className="fixed bottom-0 left-0 right-0 bg-white dark:bg-slate-800 border-t border-slate-200 dark:border-slate-700 md:hidden z-50">
|
||||||
|
<div className="flex justify-around">
|
||||||
|
{navItems.map(item => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => onTabChange(item.id)}
|
||||||
|
className={`flex-1 py-3 px-2 flex flex-col items-center justify-center gap-1 transition-colors ${
|
||||||
|
activeTab === item.id
|
||||||
|
? 'text-blue-600 dark:text-blue-400'
|
||||||
|
: 'text-slate-600 dark:text-slate-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span className="text-2xl">{item.icon}</span>
|
||||||
|
<span className="text-xs font-medium">{item.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Desktop Top Navigation */}
|
||||||
|
<nav className="hidden md:fixed md:top-0 md:left-0 md:right-0 md:bg-white md:dark:bg-slate-800 md:border-b md:border-slate-200 md:dark:border-slate-700 md:z-50 md:flex md:items-center md:justify-between md:px-6 md:py-4">
|
||||||
|
<div className="flex items-center gap-8">
|
||||||
|
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">Prayer Journal</h1>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
{navItems.map(item => (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => onTabChange(item.id)}
|
||||||
|
className={`px-4 py-2 rounded-lg font-medium transition-colors flex items-center gap-2 ${
|
||||||
|
activeTab === item.id
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>{item.icon}</span>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={logout}
|
||||||
|
className="px-4 py-2 rounded-lg font-medium text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* Mobile padding for bottom nav */}
|
||||||
|
<div className="h-20 md:h-0" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState, useMemo } from 'react';
|
||||||
|
|
||||||
|
interface Prayer {
|
||||||
|
id: string;
|
||||||
|
date: string;
|
||||||
|
text: string;
|
||||||
|
prayFor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CalendarViewProps {
|
||||||
|
prayers: Prayer[];
|
||||||
|
onDayClick: (date: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CalendarView({ prayers, onDayClick }: CalendarViewProps) {
|
||||||
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||||
|
|
||||||
|
const daysWithEntries = useMemo(() => {
|
||||||
|
return new Set(prayers.map(p => p.date));
|
||||||
|
}, [prayers]);
|
||||||
|
|
||||||
|
const getDaysInMonth = (date: Date) => {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFirstDayOfMonth = (date: Date) => {
|
||||||
|
return new Date(date.getFullYear(), date.getMonth(), 1).getDay();
|
||||||
|
};
|
||||||
|
|
||||||
|
const daysInMonth = getDaysInMonth(currentMonth);
|
||||||
|
const firstDay = getFirstDayOfMonth(currentMonth);
|
||||||
|
const prevMonth = () =>
|
||||||
|
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1));
|
||||||
|
const nextMonth = () =>
|
||||||
|
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1));
|
||||||
|
|
||||||
|
const days = [];
|
||||||
|
for (let i = 0; i < firstDay; i++) {
|
||||||
|
days.push(null);
|
||||||
|
}
|
||||||
|
for (let i = 1; i <= daysInMonth; i++) {
|
||||||
|
days.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 pt-20 px-4 pb-8">
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 dark:text-white mb-2">
|
||||||
|
Prayer Journal
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-600 dark:text-slate-400">
|
||||||
|
Click on days with entries to view them
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Calendar */}
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6">
|
||||||
|
{/* Month Navigation */}
|
||||||
|
<div className="flex justify-between items-center mb-6">
|
||||||
|
<button
|
||||||
|
onClick={prevMonth}
|
||||||
|
className="px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-900 dark:text-white transition-colors"
|
||||||
|
>
|
||||||
|
← Previous
|
||||||
|
</button>
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900 dark:text-white">
|
||||||
|
{currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })}
|
||||||
|
</h2>
|
||||||
|
<button
|
||||||
|
onClick={nextMonth}
|
||||||
|
className="px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-700 hover:bg-slate-300 dark:hover:bg-slate-600 text-slate-900 dark:text-white transition-colors"
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Day Headers */}
|
||||||
|
<div className="grid grid-cols-7 gap-2 mb-4">
|
||||||
|
{['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => (
|
||||||
|
<div key={day} className="text-center font-semibold text-slate-600 dark:text-slate-400 py-2">
|
||||||
|
{day}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Calendar Days */}
|
||||||
|
<div className="grid grid-cols-7 gap-2">
|
||||||
|
{days.map((day, index) => {
|
||||||
|
const dateString =
|
||||||
|
day !== null
|
||||||
|
? `${currentMonth.getFullYear()}-${String(currentMonth.getMonth() + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`
|
||||||
|
: null;
|
||||||
|
const hasEntry = dateString ? daysWithEntries.has(dateString) : false;
|
||||||
|
const prayerCount = dateString
|
||||||
|
? prayers.filter(p => p.date === dateString).length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
onClick={() => dateString && onDayClick(dateString)}
|
||||||
|
disabled={!day}
|
||||||
|
className={`aspect-square rounded-lg font-medium transition-all ${
|
||||||
|
!day
|
||||||
|
? 'bg-transparent cursor-default'
|
||||||
|
: hasEntry
|
||||||
|
? 'bg-blue-500 text-white hover:bg-blue-600 shadow-md'
|
||||||
|
: 'bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-white hover:bg-slate-200 dark:hover:bg-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="h-full flex flex-col items-center justify-center">
|
||||||
|
{day && (
|
||||||
|
<>
|
||||||
|
<span className="text-lg">{day}</span>
|
||||||
|
{hasEntry && (
|
||||||
|
<span className="text-xs mt-1 opacity-90">
|
||||||
|
{prayerCount} {prayerCount === 1 ? 'entry' : 'entries'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="mt-8 grid grid-cols-2 gap-4">
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-lg p-4 shadow">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 text-sm font-medium">Total Entries</p>
|
||||||
|
<p className="text-3xl font-bold text-slate-900 dark:text-white">{prayers.length}</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-lg p-4 shadow">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 text-sm font-medium">Days with Entries</p>
|
||||||
|
<p className="text-3xl font-bold text-slate-900 dark:text-white">
|
||||||
|
{daysWithEntries.size}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface DailyEntryProps {
|
||||||
|
date: string;
|
||||||
|
onDateChange: (date: string) => void;
|
||||||
|
text: string;
|
||||||
|
onTextChange: (text: string) => void;
|
||||||
|
prayFor: string;
|
||||||
|
onPrayForChange: (prayFor: string) => void;
|
||||||
|
onSubmit: (e: React.FormEvent) => void;
|
||||||
|
loading?: boolean;
|
||||||
|
isEditing?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DailyEntry({
|
||||||
|
date,
|
||||||
|
onDateChange,
|
||||||
|
text,
|
||||||
|
onTextChange,
|
||||||
|
prayFor,
|
||||||
|
onPrayForChange,
|
||||||
|
onSubmit,
|
||||||
|
loading = false,
|
||||||
|
isEditing = false,
|
||||||
|
}: DailyEntryProps) {
|
||||||
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
const isToday = date === today;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 pt-20 px-4 pb-8">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 dark:text-white mb-2">
|
||||||
|
{isToday ? "Today's Prayer" : 'Prayer Entry'}
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-600 dark:text-slate-400">
|
||||||
|
{new Date(date).toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Entry Form */}
|
||||||
|
<form onSubmit={onSubmit} className="space-y-6">
|
||||||
|
{/* Date Input */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
Date
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={date}
|
||||||
|
onChange={(e) => onDateChange(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pray For */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
Praying For (Optional)
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={prayFor}
|
||||||
|
onChange={(e) => onPrayForChange(e.target.value)}
|
||||||
|
placeholder="Person, situation, or concern..."
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Prayer Text */}
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||||
|
Your Prayer
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={text}
|
||||||
|
onChange={(e) => onTextChange(e.target.value)}
|
||||||
|
placeholder="Write your thoughts, feelings, and prayers here..."
|
||||||
|
rows={10}
|
||||||
|
required
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all resize-none font-serif"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold py-3 px-6 rounded-lg transition-all duration-200 disabled:cursor-not-allowed shadow-md hover:shadow-lg"
|
||||||
|
>
|
||||||
|
{loading ? 'Saving...' : isEditing ? 'Update Prayer' : 'Save Prayer'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Word Count */}
|
||||||
|
<div className="mt-4 text-sm text-slate-500 dark:text-slate-400 text-center">
|
||||||
|
{text.length > 0 && `${text.split(/\s+/).filter(w => w).length} words`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface Prayer {
|
||||||
|
id: string;
|
||||||
|
date: string;
|
||||||
|
text: string;
|
||||||
|
prayFor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PrayerListProps {
|
||||||
|
prayers: Prayer[];
|
||||||
|
onPrayerClick: (prayer: Prayer) => void;
|
||||||
|
onEdit: (prayer: Prayer) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PrayerList({ prayers, onPrayerClick, onEdit }: PrayerListProps) {
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [sortOrder, setSortOrder] = useState<'newest' | 'oldest'>('newest');
|
||||||
|
|
||||||
|
const filtered = prayers
|
||||||
|
.filter(
|
||||||
|
p =>
|
||||||
|
p.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
p.prayFor?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const comparison = new Date(b.date).getTime() - new Date(a.date).getTime();
|
||||||
|
return sortOrder === 'newest' ? comparison : -comparison;
|
||||||
|
});
|
||||||
|
|
||||||
|
const grouped = filtered.reduce((acc, prayer) => {
|
||||||
|
if (!acc[prayer.date]) acc[prayer.date] = [];
|
||||||
|
acc[prayer.date].push(prayer);
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, Prayer[]>);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 pt-20 px-4 pb-8">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 dark:text-white mb-2">
|
||||||
|
Past Prayers
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-600 dark:text-slate-400">
|
||||||
|
Browse and reflect on your prayer history
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filters */}
|
||||||
|
<div className="space-y-4 mb-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search prayers..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
className="w-full px-4 py-3 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
|
||||||
|
/>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setSortOrder('newest')}
|
||||||
|
className={`flex-1 px-4 py-2 rounded-lg font-medium transition-colors ${
|
||||||
|
sortOrder === 'newest'
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-slate-200 dark:bg-slate-700 text-slate-900 dark:text-white hover:bg-slate-300 dark:hover:bg-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Newest First
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setSortOrder('oldest')}
|
||||||
|
className={`flex-1 px-4 py-2 rounded-lg font-medium transition-colors ${
|
||||||
|
sortOrder === 'oldest'
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-slate-200 dark:bg-slate-700 text-slate-900 dark:text-white hover:bg-slate-300 dark:hover:bg-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Oldest First
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Prayers List */}
|
||||||
|
<div className="space-y-6">
|
||||||
|
{filtered.length === 0 ? (
|
||||||
|
<div className="text-center py-12">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 text-lg">
|
||||||
|
No prayers found. {searchTerm && 'Try adjusting your search.'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
Object.keys(grouped)
|
||||||
|
.sort()
|
||||||
|
.reverse()
|
||||||
|
.map(date => (
|
||||||
|
<div key={date}>
|
||||||
|
<h2 className="text-sm font-semibold text-slate-500 dark:text-slate-400 mb-3 uppercase tracking-wider">
|
||||||
|
{new Date(date).toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: grouped[date].some(
|
||||||
|
p => new Date(p.date).getFullYear() !== new Date().getFullYear()
|
||||||
|
)
|
||||||
|
? 'numeric'
|
||||||
|
: undefined,
|
||||||
|
})}
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{grouped[date].map(prayer => (
|
||||||
|
<div
|
||||||
|
key={prayer.id}
|
||||||
|
className="bg-white dark:bg-slate-800 rounded-lg p-6 shadow hover:shadow-lg transition-all cursor-pointer border border-slate-200 dark:border-slate-700"
|
||||||
|
onClick={() => onPrayerClick(prayer)}
|
||||||
|
>
|
||||||
|
{prayer.prayFor && (
|
||||||
|
<p className="text-sm font-medium text-blue-600 dark:text-blue-400 mb-2">
|
||||||
|
Praying for: {prayer.prayFor}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<p className="text-slate-900 dark:text-white line-clamp-3">
|
||||||
|
{prayer.text}
|
||||||
|
</p>
|
||||||
|
<div className="mt-4 flex justify-between items-center">
|
||||||
|
<span className="text-xs text-slate-500 dark:text-slate-400">
|
||||||
|
{new Date(date).toLocaleTimeString('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onEdit(prayer);
|
||||||
|
}}
|
||||||
|
className="text-xs px-3 py-1 bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-400 rounded hover:bg-blue-200 dark:hover:bg-blue-800 transition-colors"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
|
interface SettingsPageProps {
|
||||||
|
userEmail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SettingsPage({ userEmail }: SettingsPageProps) {
|
||||||
|
const { logout } = useAuth();
|
||||||
|
const { theme, setTheme } = useTheme();
|
||||||
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 pt-20 px-4 pb-8">
|
||||||
|
<div className="max-w-2xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="text-center mb-8">
|
||||||
|
<h1 className="text-4xl font-bold text-slate-900 dark:text-white mb-2">
|
||||||
|
Settings
|
||||||
|
</h1>
|
||||||
|
<p className="text-slate-600 dark:text-slate-400">
|
||||||
|
Manage your preferences and account
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Settings Card */}
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-xl shadow-lg p-6 space-y-6">
|
||||||
|
{/* Account Section */}
|
||||||
|
<div className="border-b border-slate-200 dark:border-slate-700 pb-6">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-900 dark:text-white mb-4">
|
||||||
|
Account
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm font-medium text-slate-600 dark:text-slate-400">Email</p>
|
||||||
|
<p className="text-slate-900 dark:text-white">{userEmail || 'Not set'}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Appearance Section */}
|
||||||
|
<div className="border-b border-slate-200 dark:border-slate-700 pb-6">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-900 dark:text-white mb-4">
|
||||||
|
Appearance
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<p className="text-sm font-medium text-slate-600 dark:text-slate-400 mb-2">
|
||||||
|
Theme
|
||||||
|
</p>
|
||||||
|
<div className="grid grid-cols-3 gap-2">
|
||||||
|
{['light', 'dark', 'system'].map(t => (
|
||||||
|
<button
|
||||||
|
key={t}
|
||||||
|
onClick={() => setTheme(t)}
|
||||||
|
className={`px-4 py-2 rounded-lg font-medium transition-colors capitalize ${
|
||||||
|
theme === t
|
||||||
|
? 'bg-blue-600 text-white'
|
||||||
|
: 'bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-white hover:bg-slate-200 dark:hover:bg-slate-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{t === 'system' ? 'Auto' : t}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* About Section */}
|
||||||
|
<div className="border-b border-slate-200 dark:border-slate-700 pb-6">
|
||||||
|
<h2 className="text-lg font-semibold text-slate-900 dark:text-white mb-4">
|
||||||
|
About
|
||||||
|
</h2>
|
||||||
|
<div className="space-y-2 text-sm text-slate-600 dark:text-slate-400">
|
||||||
|
<p>Prayer Journal v1.0</p>
|
||||||
|
<p>A simple, private space for your daily prayers and reflections.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logout Section */}
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowConfirm(true)}
|
||||||
|
className="w-full px-4 py-3 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Logout Confirmation Modal */}
|
||||||
|
{showConfirm && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-xl p-6 max-w-sm w-full">
|
||||||
|
<h2 className="text-xl font-bold text-slate-900 dark:text-white mb-2">
|
||||||
|
Logout?
|
||||||
|
</h2>
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 mb-6">
|
||||||
|
Are you sure you want to logout?
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => setShowConfirm(false)}
|
||||||
|
className="flex-1 px-4 py-2 rounded-lg bg-slate-200 dark:bg-slate-700 text-slate-900 dark:text-white font-medium hover:bg-slate-300 dark:hover:bg-slate-600 transition-colors"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => logout()}
|
||||||
|
className="flex-1 px-4 py-2 rounded-lg bg-red-600 text-white font-medium hover:bg-red-700 transition-colors"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user