Add AI chat feature for verse explanations and fix login redirect handling

Bible Reader Enhancements:
- Add chat icon to each verse for AI explanations
- Implement handleVerseChat function with pre-filled contextual messages
- Chat opens with message: "Explain in depth this verse [text] from [version], [book] [chapter:verse] and its meaning"
- Visible to all users, redirects to login for unauthenticated users
- Fix copy message translation from 'bible.copied' to 'copied'

Login System Improvements:
- Fix redirect parameter handling in login pages
- Users are now properly redirected to /bible page after successful login
- Preserve redirect URL parameters through login flow
- Add Suspense boundaries for useSearchParams compliance
- Ensure verse/chapter context is maintained after login

Technical Changes:
- Add Chat icon import from Material-UI
- Implement floating chat event dispatch system
- Fix Next.js 15 build warnings with proper Suspense wrapping
- Maintain existing UX patterns (visible to all, functional for authenticated users)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-24 20:11:35 +00:00
parent 6913206560
commit 68528eec73
3 changed files with 88 additions and 13 deletions

View File

@@ -1,7 +1,7 @@
'use client' 'use client'
import { useState } from 'react' import { useState, Suspense } from 'react'
import { useRouter } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { useTranslations, useLocale } from 'next-intl' import { useTranslations, useLocale } from 'next-intl'
import { import {
Container, Container,
@@ -13,7 +13,8 @@ import {
Link, Link,
Card, Card,
CardContent, CardContent,
Divider Divider,
CircularProgress
} from '@mui/material' } from '@mui/material'
import { import {
MenuBook, MenuBook,
@@ -23,14 +24,20 @@ import {
import { LoginForm } from '@/components/auth/login-form' import { LoginForm } from '@/components/auth/login-form'
import { RegisterForm } from '@/components/auth/register-form' import { RegisterForm } from '@/components/auth/register-form'
export default function AuthPage() { function AuthContent() {
const [activeTab, setActiveTab] = useState(0) const [activeTab, setActiveTab] = useState(0)
const router = useRouter() const router = useRouter()
const locale = useLocale() const locale = useLocale()
const searchParams = useSearchParams()
const t = useTranslations('auth') const t = useTranslations('auth')
const handleAuthSuccess = () => { const handleAuthSuccess = () => {
router.push(`/${locale}`) const redirect = searchParams.get('redirect')
if (redirect) {
router.push(redirect)
} else {
router.push(`/${locale}`)
}
} }
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
@@ -130,4 +137,18 @@ export default function AuthPage() {
</Card> </Card>
</Container> </Container>
) )
}
export default function AuthPage() {
return (
<Suspense fallback={
<Container maxWidth="sm" sx={{ mt: 8 }}>
<Box display="flex" justifyContent="center" alignItems="center" minHeight="400px">
<CircularProgress />
</Box>
</Container>
}>
<AuthContent />
</Suspense>
)
} }

View File

@@ -68,7 +68,8 @@ import {
ExpandMore, ExpandMore,
MenuBook, MenuBook,
Visibility, Visibility,
Speed Speed,
Chat
} from '@mui/icons-material' } from '@mui/icons-material'
interface BibleVerse { interface BibleVerse {
@@ -664,11 +665,32 @@ export default function BibleReaderNew() {
navigator.clipboard.writeText(text).then(() => { navigator.clipboard.writeText(text).then(() => {
setCopyFeedback({ setCopyFeedback({
open: true, open: true,
message: t('bible.copied') message: t('copied')
}) })
}) })
} }
const handleVerseChat = (verse: BibleVerse) => {
// If user is not authenticated, redirect to login
if (!user) {
router.push(`/${locale}/login?redirect=${encodeURIComponent(`/${locale}/bible?version=${selectedVersion}&book=${selectedBook}&chapter=${selectedChapter}&verse=${verse.verseNum}`)}`)
return
}
const versionName = versions.find(v => v.id === selectedVersion)?.name || selectedVersion
const bookName = currentBook?.name || 'Unknown Book'
const initialMessage = `Explain in depth this verse "${verse.text}" from ${versionName}, ${bookName} ${selectedChapter}:${verse.verseNum} and its meaning`
// Dispatch event to open floating chat with the pre-filled message
window.dispatchEvent(new CustomEvent('floating-chat:open', {
detail: {
initialMessage: initialMessage,
fullscreen: false
}
}))
}
const getThemeStyles = () => { const getThemeStyles = () => {
switch (preferences.theme) { switch (preferences.theme) {
case 'dark': case 'dark':
@@ -765,6 +787,13 @@ export default function BibleReaderNew() {
> >
<ContentCopy fontSize="small" /> <ContentCopy fontSize="small" />
</IconButton> </IconButton>
<IconButton
size="small"
onClick={() => handleVerseChat(verse)}
sx={{ color: 'action.active' }}
>
<Chat fontSize="small" />
</IconButton>
</Box> </Box>
)} )}
</Box> </Box>

View File

@@ -1,18 +1,21 @@
'use client' 'use client'
import { useEffect } from 'react' import { useEffect, Suspense } from 'react'
import { useRouter } from 'next/navigation' import { useRouter, useSearchParams } from 'next/navigation'
import { useLocale } from 'next-intl' import { useLocale } from 'next-intl'
import { Box, CircularProgress, Typography } from '@mui/material' import { Box, CircularProgress, Typography } from '@mui/material'
export default function LoginRedirectPage() { function LoginRedirectContent() {
const router = useRouter() const router = useRouter()
const locale = useLocale() const locale = useLocale()
const searchParams = useSearchParams()
useEffect(() => { useEffect(() => {
// Redirect to the actual login page // Preserve the redirect parameter when redirecting to the actual login page
router.replace(`/${locale}/auth/login`) const redirect = searchParams.get('redirect')
}, [router, locale]) const redirectParam = redirect ? `?redirect=${encodeURIComponent(redirect)}` : ''
router.replace(`/${locale}/auth/login${redirectParam}`)
}, [router, locale, searchParams])
return ( return (
<Box <Box
@@ -29,4 +32,26 @@ export default function LoginRedirectPage() {
</Typography> </Typography>
</Box> </Box>
) )
}
export default function LoginRedirectPage() {
return (
<Suspense fallback={
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
minHeight="100vh"
gap={2}
>
<CircularProgress />
<Typography variant="body2" color="text.secondary">
Loading...
</Typography>
</Box>
}>
<LoginRedirectContent />
</Suspense>
)
} }