Require user authentication for AI chat functionality

- Update chat API to require valid authentication tokens for all requests
- Add authentication requirement screens to both chat components
- Show "Create Account / Sign In" prompts for unauthenticated users
- Hide chat input and functionality until user is logged in
- Return 401 errors with clear messages when authentication is missing
- Maintain bilingual support (Romanian/English) for auth prompts

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-28 20:22:57 +00:00
parent e4b815cb40
commit 83a981cabc
3 changed files with 182 additions and 83 deletions

View File

@@ -1,13 +1,14 @@
'use client'
import { useState, useRef, useEffect } from 'react'
import { Send } from 'lucide-react'
import { Send, User } from 'lucide-react'
import ReactMarkdown from 'react-markdown'
export function ChatInterface() {
const [messages, setMessages] = useState<Array<{ role: string; content: string }>>([])
const [input, setInput] = useState('')
const [loading, setLoading] = useState(false)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const scrollToBottom = () => {
@@ -16,6 +17,24 @@ export function ChatInterface() {
useEffect(scrollToBottom, [messages])
// Check authentication status on mount
useEffect(() => {
const checkAuth = async () => {
try {
const token = localStorage.getItem('authToken')
if (token) {
const response = await fetch('/api/auth/me', {
headers: { 'Authorization': `Bearer ${token}` }
})
setIsAuthenticated(response.ok)
}
} catch (error) {
setIsAuthenticated(false)
}
}
checkAuth()
}, [])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!input.trim() || loading) return
@@ -64,14 +83,32 @@ export function ChatInterface() {
</div>
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.length === 0 && (
<div className="text-center text-gray-500 mt-12">
<p>Bună ziua! Sunt aici ajut cu întrebările despre Biblie.</p>
<p className="text-sm mt-2">Puteți începe prin a întreba ceva despre un verset sau o temă biblică.</p>
{!isAuthenticated ? (
<div className="flex flex-col items-center justify-center h-full text-center p-6">
<User className="w-16 h-16 text-gray-400 mb-4" />
<h3 className="text-lg font-semibold mb-2">Bun venit la AI Chat Biblic!</h3>
<p className="text-gray-600 mb-4 max-w-md">
Pentru a accesa chat-ul AI și a salva conversațiile tale, te rugăm îți creezi un cont sau te conectezi.
</p>
<button
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors"
>
Creează Cont / Conectează-te
</button>
</div>
)}
) : (
<>
{messages.length === 0 && (
<div className="text-center text-gray-500 mt-12">
<p>Bună ziua! Sunt aici ajut cu întrebările despre Biblie.</p>
<p className="text-sm mt-2">Puteți începe prin a întreba ceva despre un verset sau o temă biblică.</p>
</div>
)}
{messages.map((msg, idx) => (
{messages.map((msg, idx) => (
<div
key={idx}
className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}
@@ -106,29 +143,33 @@ export function ChatInterface() {
</div>
</div>
</div>
)}
<div ref={messagesEndRef} />
</>
)}
<div ref={messagesEndRef} />
</div>
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Întreabă despre Biblie..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
disabled={loading}
/>
<button
type="submit"
disabled={loading || !input.trim()}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
<Send className="w-5 h-5" />
</button>
</div>
</form>
{isAuthenticated && (
<form onSubmit={handleSubmit} className="p-4 border-t">
<div className="flex space-x-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Întreabă despre Biblie..."
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
disabled={loading}
/>
<button
type="submit"
disabled={loading || !input.trim()}
className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
<Send className="w-5 h-5" />
</button>
</div>
</form>
)}
</div>
)
}

View File

@@ -577,10 +577,16 @@ export default function FloatingChat() {
{!isAuthenticated ? (
<Box sx={{ textAlign: 'center', py: 3 }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Sign in to save your conversations
{locale === 'ro' ? 'Conectează-te pentru a salva conversațiile' : 'Sign in to save your conversations'}
</Typography>
<Button variant="outlined" size="small">
Sign In
<Button
variant="outlined"
size="small"
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
>
{locale === 'ro' ? 'Conectează-te' : 'Sign In'}
</Button>
</Box>
) : isLoadingConversations ? (
@@ -688,7 +694,44 @@ export default function FloatingChat() {
p: 1,
}}
>
{messages.map((message) => (
{!isAuthenticated ? (
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
textAlign: 'center',
p: 3
}}>
<SmartToy sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" gutterBottom>
{locale === 'ro' ? 'Bun venit la AI Chat Biblic!' : 'Welcome to Biblical AI Chat!'}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3, maxWidth: 400 }}>
{locale === 'ro'
? 'Pentru a accesa chat-ul AI și a salva conversațiile tale, te rugăm să îți creezi un cont sau să te conectezi.'
: 'To access the AI chat and save your conversations, please create an account or sign in.'
}
</Typography>
<Button
variant="contained"
size="large"
onClick={() => {
window.dispatchEvent(new CustomEvent('auth:sign-in-required'))
}}
sx={{
background: 'linear-gradient(45deg, #009688 30%, #00796B 90%)',
px: 4,
py: 1.5
}}
>
{locale === 'ro' ? 'Creează Cont / Conectează-te' : 'Create Account / Sign In'}
</Button>
</Box>
) : (
<>
{messages.map((message) => (
<Box
key={message.id}
sx={{
@@ -834,49 +877,53 @@ export default function FloatingChat() {
</Box>
)}
<div ref={messagesEndRef} />
<div ref={messagesEndRef} />
</>
)}
</Box>
<Divider />
{/* Input */}
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', gap: 1 }}>
<TextField
fullWidth
size="small"
multiline
maxRows={3}
placeholder={t('placeholder')}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
disabled={isLoading}
variant="outlined"
sx={{
'& .MuiOutlinedInput-root': {
{isAuthenticated && (
<Box sx={{ p: 2 }}>
<Box sx={{ display: 'flex', gap: 1 }}>
<TextField
fullWidth
size="small"
multiline
maxRows={3}
placeholder={t('placeholder')}
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={handleKeyPress}
disabled={isLoading}
variant="outlined"
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: 2,
}
}}
/>
<Button
variant="contained"
onClick={handleSendMessage}
disabled={!inputMessage.trim() || isLoading}
sx={{
minWidth: 'auto',
px: 2,
borderRadius: 2,
}
}}
/>
<Button
variant="contained"
onClick={handleSendMessage}
disabled={!inputMessage.trim() || isLoading}
sx={{
minWidth: 'auto',
px: 2,
borderRadius: 2,
background: 'linear-gradient(45deg, #009688 30%, #00796B 90%)',
}}
>
<Send fontSize="small" />
</Button>
background: 'linear-gradient(45deg, #009688 30%, #00796B 90%)',
}}
>
<Send fontSize="small" />
</Button>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
{t('enterToSend')}
</Typography>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ mt: 0.5, display: 'block' }}>
{t('enterToSend')}
</Typography>
</Box>
)}
</Box>
</Box>