Implement comprehensive prayer writing and AI generation interface
- Add authentication-gated prayer creation with dual-mode interface - Implement tabbed dialog: "Write Prayer" and "AI Generate" options - Create AI prayer generation API endpoint with bilingual support (EN/RO) - Add category-specific prayer templates with user prompt integration - Include proper authentication checks and user-based prayer attribution - Enhance UX with loading states, success feedback, and guided prompts - Fix missing searchTypes translations in English locale for search functionality - Restrict prayer creation to logged-in users with appropriate visual feedback - Support both manual composition and AI-assisted prayer creation workflows 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,11 @@ import {
|
||||
useTheme,
|
||||
CircularProgress,
|
||||
Skeleton,
|
||||
Alert,
|
||||
Tabs,
|
||||
Tab,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
} from '@mui/material'
|
||||
import {
|
||||
Favorite,
|
||||
@@ -35,9 +40,13 @@ import {
|
||||
FavoriteBorder,
|
||||
Share,
|
||||
MoreVert,
|
||||
AutoAwesome,
|
||||
Edit,
|
||||
Login,
|
||||
} from '@mui/icons-material'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useTranslations, useLocale, useFormatter } from 'next-intl'
|
||||
import { useAuth } from '@/hooks/use-auth'
|
||||
|
||||
interface PrayerRequest {
|
||||
id: string
|
||||
@@ -56,13 +65,17 @@ export default function PrayersPage() {
|
||||
const t = useTranslations('pages.prayers')
|
||||
const tc = useTranslations('common')
|
||||
const f = useFormatter()
|
||||
const { user } = useAuth()
|
||||
const [prayers, setPrayers] = useState<PrayerRequest[]>([])
|
||||
const [openDialog, setOpenDialog] = useState(false)
|
||||
const [tabValue, setTabValue] = useState(0) // 0 = Write, 1 = AI Generate
|
||||
const [newPrayer, setNewPrayer] = useState({
|
||||
title: '',
|
||||
description: '',
|
||||
category: 'personal',
|
||||
})
|
||||
const [aiPrompt, setAiPrompt] = useState('')
|
||||
const [isGenerating, setIsGenerating] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
const categories = [
|
||||
@@ -114,15 +127,53 @@ export default function PrayersPage() {
|
||||
}, 1000)
|
||||
}, [locale])
|
||||
|
||||
const handleGenerateAIPrayer = async () => {
|
||||
if (!aiPrompt.trim()) return
|
||||
if (!user) return
|
||||
|
||||
setIsGenerating(true)
|
||||
try {
|
||||
const response = await fetch('/api/prayers/generate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: aiPrompt,
|
||||
category: newPrayer.category,
|
||||
locale
|
||||
}),
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setNewPrayer({
|
||||
title: data.title || '',
|
||||
description: data.prayer || '',
|
||||
category: newPrayer.category
|
||||
})
|
||||
setTabValue(0) // Switch to write tab to review generated prayer
|
||||
} else {
|
||||
console.error('Failed to generate prayer')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating prayer:', error)
|
||||
} finally {
|
||||
setIsGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmitPrayer = async () => {
|
||||
if (!newPrayer.title.trim() || !newPrayer.description.trim()) return
|
||||
if (!user) return
|
||||
|
||||
const prayer: PrayerRequest = {
|
||||
id: Date.now().toString(),
|
||||
title: newPrayer.title,
|
||||
description: newPrayer.description,
|
||||
category: newPrayer.category,
|
||||
author: locale === 'en' ? 'You' : 'Tu', // In real app, get from auth
|
||||
author: user.name || (locale === 'en' ? 'You' : 'Tu'),
|
||||
timestamp: new Date(),
|
||||
prayerCount: 0,
|
||||
isPrayedFor: false,
|
||||
@@ -130,13 +181,18 @@ export default function PrayersPage() {
|
||||
|
||||
setPrayers([prayer, ...prayers])
|
||||
setNewPrayer({ title: '', description: '', category: 'personal' })
|
||||
setAiPrompt('')
|
||||
setTabValue(0)
|
||||
setOpenDialog(false)
|
||||
|
||||
// In real app, send to API
|
||||
try {
|
||||
await fetch('/api/prayers', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('authToken')}`
|
||||
},
|
||||
body: JSON.stringify(prayer),
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -144,6 +200,14 @@ export default function PrayersPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenDialog = () => {
|
||||
if (!user) {
|
||||
// Could redirect to login or show login modal
|
||||
return
|
||||
}
|
||||
setOpenDialog(true)
|
||||
}
|
||||
|
||||
const handlePrayFor = async (prayerId: string) => {
|
||||
setPrayers(prayers.map(prayer =>
|
||||
prayer.id === prayerId
|
||||
@@ -340,20 +404,34 @@ export default function PrayersPage() {
|
||||
</Grid>
|
||||
|
||||
{/* Add Prayer FAB */}
|
||||
<Fab
|
||||
color="primary"
|
||||
aria-label="add prayer"
|
||||
sx={{ position: 'fixed', bottom: 24, right: 24 }}
|
||||
onClick={() => setOpenDialog(true)}
|
||||
>
|
||||
<Add />
|
||||
</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}
|
||||
onClose={() => setOpenDialog(false)}
|
||||
maxWidth="sm"
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
@@ -364,40 +442,127 @@ export default function PrayersPage() {
|
||||
</IconButton>
|
||||
</Box>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.titleLabel')}
|
||||
value={newPrayer.title}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, title: e.target.value })}
|
||||
sx={{ mb: 2, mt: 1 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.categoryLabel')}
|
||||
select
|
||||
value={newPrayer.category}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, category: e.target.value })}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{/* Tabs for Write vs AI Generate */}
|
||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tabs value={tabValue} onChange={(e, newValue) => setTabValue(newValue)} centered>
|
||||
<Tab
|
||||
icon={<Edit />}
|
||||
label={locale === 'en' ? 'Write Prayer' : 'Scrie rugăciune'}
|
||||
iconPosition="start"
|
||||
/>
|
||||
<Tab
|
||||
icon={<AutoAwesome />}
|
||||
label={locale === 'en' ? 'AI Generate' : 'Generează cu AI'}
|
||||
iconPosition="start"
|
||||
/>
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.descriptionLabel')}
|
||||
multiline
|
||||
rows={4}
|
||||
value={newPrayer.description}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, description: e.target.value })}
|
||||
placeholder={t('dialog.placeholder')}
|
||||
/>
|
||||
<DialogContent sx={{ minHeight: 400 }}>
|
||||
{/* Write Prayer Tab */}
|
||||
{tabValue === 0 && (
|
||||
<Box>
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.titleLabel')}
|
||||
value={newPrayer.title}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, title: e.target.value })}
|
||||
sx={{ mb: 2, mt: 1 }}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.categoryLabel')}
|
||||
select
|
||||
value={newPrayer.category}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, category: e.target.value })}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.descriptionLabel')}
|
||||
multiline
|
||||
rows={6}
|
||||
value={newPrayer.description}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, description: e.target.value })}
|
||||
placeholder={t('dialog.placeholder')}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* AI Generate Prayer Tab */}
|
||||
{tabValue === 1 && (
|
||||
<Box>
|
||||
<Alert severity="info" sx={{ mb: 2 }}>
|
||||
{locale === 'en'
|
||||
? 'Describe what you\'d like to pray about, and AI will help you create a meaningful prayer.'
|
||||
: 'Descrie pentru ce ai vrea să te rogi, iar AI-ul te va ajuta să creezi o rugăciune semnificativă.'
|
||||
}
|
||||
</Alert>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={t('dialog.categoryLabel')}
|
||||
select
|
||||
value={newPrayer.category}
|
||||
onChange={(e) => setNewPrayer({ ...newPrayer, category: e.target.value })}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
{categories.map((option) => (
|
||||
<MenuItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label={locale === 'en' ? 'What would you like to pray about?' : 'Pentru ce ai vrea să te rogi?'}
|
||||
multiline
|
||||
rows={4}
|
||||
value={aiPrompt}
|
||||
onChange={(e) => setAiPrompt(e.target.value)}
|
||||
placeholder={locale === 'en'
|
||||
? 'e.g., "Help me find peace during a difficult time at work" or "Guidance for my family\'s health struggles"'
|
||||
: 'ex. "Ajută-mă să găsesc pace într-o perioadă dificilă la muncă" sau "Îndrumarea pentru problemele de sănătate ale familiei mele"'
|
||||
}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
onClick={handleGenerateAIPrayer}
|
||||
disabled={!aiPrompt.trim() || isGenerating}
|
||||
startIcon={isGenerating ? <CircularProgress size={20} /> : <AutoAwesome />}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
{isGenerating
|
||||
? (locale === 'en' ? 'Generating...' : 'Se generează...')
|
||||
: (locale === 'en' ? 'Generate Prayer with AI' : 'Generează rugăciune cu AI')
|
||||
}
|
||||
</Button>
|
||||
|
||||
{newPrayer.title && newPrayer.description && (
|
||||
<Alert severity="success" sx={{ mt: 2 }}>
|
||||
{locale === 'en'
|
||||
? 'Prayer generated! Switch to the "Write Prayer" tab to review and edit before submitting.'
|
||||
: 'Rugăciune generată! Comută la tabul "Scrie rugăciune" pentru a revizui și edita înainte de a trimite.'
|
||||
}
|
||||
</Alert>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button onClick={() => setOpenDialog(false)}>
|
||||
{t('dialog.cancel')}
|
||||
|
||||
Reference in New Issue
Block a user