Move Add Prayer button to sidebar and implement comprehensive prayer functionality
- Integrated prayer data with Prisma database schema - Updated API endpoints to use actual database - Implemented AI prayer generation with Azure OpenAI - Added user authentication for prayer creation - Moved Add Prayer button from FAB to sidebar top - Added prayer count tracking and user prayer status 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Fab,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
@@ -45,7 +44,7 @@ import {
|
||||
Login,
|
||||
} from '@mui/icons-material'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations, useLocale, useFormatter } from 'next-intl'
|
||||
import { useTranslations, useLocale, useFormatter, useNow } from 'next-intl'
|
||||
import { useAuth } from '@/hooks/use-auth'
|
||||
|
||||
interface PrayerRequest {
|
||||
@@ -65,8 +64,10 @@ export default function PrayersPage() {
|
||||
const t = useTranslations('pages.prayers')
|
||||
const tc = useTranslations('common')
|
||||
const f = useFormatter()
|
||||
const now = useNow()
|
||||
const { user } = useAuth()
|
||||
const [prayers, setPrayers] = useState<PrayerRequest[]>([])
|
||||
const [selectedCategory, setSelectedCategory] = useState<string>('all')
|
||||
const [openDialog, setOpenDialog] = useState(false)
|
||||
const [tabValue, setTabValue] = useState(0) // 0 = Write, 1 = AI Generate
|
||||
const [newPrayer, setNewPrayer] = useState({
|
||||
@@ -87,45 +88,39 @@ export default function PrayersPage() {
|
||||
{ value: 'world', label: t('categories.world'), color: 'info' },
|
||||
]
|
||||
|
||||
// Sample data - in real app this would come from API
|
||||
useEffect(() => {
|
||||
// Simulate loading prayers
|
||||
setTimeout(() => {
|
||||
setPrayers([
|
||||
{
|
||||
id: '1',
|
||||
title: t('samples.item1.title'),
|
||||
description: t('samples.item1.description'),
|
||||
category: 'health',
|
||||
author: t('samples.item1.author'),
|
||||
timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000),
|
||||
prayerCount: 23,
|
||||
isPrayedFor: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: t('samples.item2.title'),
|
||||
description: t('samples.item2.description'),
|
||||
category: 'work',
|
||||
author: t('samples.item2.author'),
|
||||
timestamp: new Date(Date.now() - 5 * 60 * 60 * 1000),
|
||||
prayerCount: 15,
|
||||
isPrayedFor: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: t('samples.item3.title'),
|
||||
description: t('samples.item3.description'),
|
||||
category: 'family',
|
||||
author: t('samples.item3.author'),
|
||||
timestamp: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
|
||||
prayerCount: 41,
|
||||
isPrayedFor: false,
|
||||
},
|
||||
])
|
||||
// Fetch prayers from API
|
||||
const fetchPrayers = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const params = new URLSearchParams()
|
||||
if (selectedCategory !== 'all') {
|
||||
params.append('category', selectedCategory)
|
||||
}
|
||||
params.append('limit', '50')
|
||||
if (user?.id) {
|
||||
params.append('userId', user.id)
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/prayers?${params.toString()}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setPrayers(data.prayers.map((prayer: any) => ({
|
||||
...prayer,
|
||||
timestamp: new Date(prayer.timestamp)
|
||||
})))
|
||||
} else {
|
||||
console.error('Failed to fetch prayers')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching prayers:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
}, [locale])
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrayers()
|
||||
}, [selectedCategory, user])
|
||||
|
||||
const handleGenerateAIPrayer = async () => {
|
||||
if (!aiPrompt.trim()) return
|
||||
@@ -179,22 +174,34 @@ export default function PrayersPage() {
|
||||
isPrayedFor: false,
|
||||
}
|
||||
|
||||
setPrayers([prayer, ...prayers])
|
||||
setNewPrayer({ title: '', description: '', category: 'personal' })
|
||||
setAiPrompt('')
|
||||
setTabValue(0)
|
||||
setOpenDialog(false)
|
||||
|
||||
// In real app, send to API
|
||||
try {
|
||||
await fetch('/api/prayers', {
|
||||
const response = await fetch('/api/prayers', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
|
||||
},
|
||||
body: JSON.stringify(prayer),
|
||||
body: JSON.stringify({
|
||||
title: newPrayer.title,
|
||||
description: newPrayer.description,
|
||||
category: newPrayer.category,
|
||||
isAnonymous: false
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setPrayers([{
|
||||
...data.prayer,
|
||||
timestamp: new Date(data.prayer.timestamp)
|
||||
}, ...prayers])
|
||||
setNewPrayer({ title: '', description: '', category: 'personal' })
|
||||
setAiPrompt('')
|
||||
setTabValue(0)
|
||||
setOpenDialog(false)
|
||||
} else {
|
||||
console.error('Failed to submit prayer')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error submitting prayer:', error)
|
||||
}
|
||||
@@ -209,15 +216,31 @@ export default function PrayersPage() {
|
||||
}
|
||||
|
||||
const handlePrayFor = async (prayerId: string) => {
|
||||
setPrayers(prayers.map(prayer =>
|
||||
prayer.id === prayerId
|
||||
? { ...prayer, prayerCount: prayer.prayerCount + 1, isPrayedFor: true }
|
||||
: prayer
|
||||
))
|
||||
|
||||
// In real app, send to API
|
||||
try {
|
||||
await fetch(`/api/prayers/${prayerId}/pray`, { method: 'POST' })
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
const authToken = localStorage.getItem('authToken')
|
||||
if (authToken) {
|
||||
headers['Authorization'] = `Bearer ${authToken}`
|
||||
}
|
||||
|
||||
const response = await fetch(`/api/prayers/${prayerId}/pray`, {
|
||||
method: 'POST',
|
||||
headers
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setPrayers(prayers.map(prayer =>
|
||||
prayer.id === prayerId
|
||||
? { ...prayer, prayerCount: data.prayerCount || prayer.prayerCount + 1, isPrayedFor: true }
|
||||
: prayer
|
||||
))
|
||||
} else {
|
||||
console.error('Failed to update prayer count')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating prayer count:', error)
|
||||
}
|
||||
@@ -228,7 +251,6 @@ export default function PrayersPage() {
|
||||
}
|
||||
|
||||
const formatTimestamp = (timestamp: Date) => {
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - timestamp.getTime()
|
||||
const minutes = Math.floor(diff / (1000 * 60))
|
||||
const hours = Math.floor(minutes / 60)
|
||||
@@ -260,18 +282,55 @@ export default function PrayersPage() {
|
||||
<Grid item xs={12} md={3}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
{/* Add Prayer Button */}
|
||||
{user ? (
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<Add />}
|
||||
onClick={handleOpenDialog}
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
{t('dialog.title')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<Login />}
|
||||
onClick={() => {
|
||||
// Could redirect to login page or show login modal
|
||||
console.log('Please login to add prayers')
|
||||
}}
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
{locale === 'en' ? 'Login to Add Prayer' : 'Conectează-te pentru a adăuga'}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Typography variant="h6" gutterBottom>
|
||||
{t('categories.title')}
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Chip
|
||||
label={t('categories.all', { defaultValue: 'All' })}
|
||||
color="default"
|
||||
variant={selectedCategory === 'all' ? 'filled' : 'outlined'}
|
||||
size="small"
|
||||
onClick={() => setSelectedCategory('all')}
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
/>
|
||||
{categories.map((category) => (
|
||||
<Chip
|
||||
key={category.value}
|
||||
label={category.label}
|
||||
color={category.color as any}
|
||||
variant="outlined"
|
||||
variant={selectedCategory === category.value ? 'filled' : 'outlined'}
|
||||
size="small"
|
||||
sx={{ justifyContent: 'flex-start' }}
|
||||
onClick={() => setSelectedCategory(category.value)}
|
||||
sx={{ justifyContent: 'flex-start', cursor: 'pointer' }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
@@ -403,30 +462,6 @@ export default function PrayersPage() {
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Add Prayer FAB */}
|
||||
{user ? (
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add prayer"
|
||||
sx={{ position: 'fixed', bottom: 24, right: 24 }}
|
||||
onClick={handleOpenDialog}
|
||||
>
|
||||
<Add />
|
||||
</Fab>
|
||||
) : (
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="login to add prayer"
|
||||
sx={{ position: 'fixed', bottom: 24, right: 24 }}
|
||||
onClick={() => {
|
||||
// Could redirect to login page or show login modal
|
||||
console.log('Please login to add prayers')
|
||||
}}
|
||||
>
|
||||
<Login />
|
||||
</Fab>
|
||||
)}
|
||||
|
||||
{/* Add Prayer Dialog */}
|
||||
<Dialog
|
||||
open={openDialog}
|
||||
|
||||
Reference in New Issue
Block a user