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:
andupetcu
2025-09-21 23:17:20 +03:00
parent 6492601355
commit 5a3c6a6691
7 changed files with 669 additions and 259 deletions

View File

@@ -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}