← back to Dear Bubbe Nextjs
app/messages/page.tsx
240 lines
'use client'
import { useState, useEffect } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
interface ConversationMessage {
timestamp: string
message: string
response: string
context?: string
}
interface UserMemory {
userId: string
lastVisit: string
totalMessages: number
topics: string[]
conversations: ConversationMessage[]
}
export default function MessagesPage() {
const { data: session, status } = useSession()
const router = useRouter()
const [memory, setMemory] = useState<UserMemory | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [selectedTopic, setSelectedTopic] = useState<string>('all')
const [searchQuery, setSearchQuery] = useState('')
// Redirect if not logged in
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/')
}
}, [status, router])
// Load conversation history
useEffect(() => {
const loadMessages = async () => {
if (!session?.user?.email) return
try {
const response = await fetch(`/api/messages?userId=${encodeURIComponent(session.user.email)}`)
if (response.ok) {
const data = await response.json()
setMemory(data)
}
} catch (error) {
console.error('Failed to load messages:', error)
} finally {
setIsLoading(false)
}
}
if (session) {
loadMessages()
}
}, [session])
// Filter conversations
const filteredConversations = memory?.conversations.filter(conv => {
const matchesSearch = searchQuery === '' ||
conv.message.toLowerCase().includes(searchQuery.toLowerCase()) ||
conv.response.toLowerCase().includes(searchQuery.toLowerCase())
const matchesTopic = selectedTopic === 'all' ||
(conv.context && conv.context.includes(selectedTopic))
return matchesSearch && matchesTopic
}) || []
if (status === 'loading' || isLoading) {
return (
<div className="min-h-screen bg-gradient-to-br from-amber-50 to-orange-100 flex items-center justify-center">
<div className="text-2xl text-amber-600">Loading your conversations...</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-amber-50 to-orange-100 py-8">
<div className="max-w-5xl mx-auto px-4">
{/* Header */}
<div className="mb-8">
<Link href="/" className="text-amber-600 hover:text-amber-700 mb-4 inline-block">
← Back to Bubbe
</Link>
<h1 className="text-3xl font-bold text-gray-800">My Conversations with Bubbe</h1>
<div className="mt-2 text-gray-600">
{memory && (
<>
<p>Total messages: {memory.totalMessages}</p>
<p>Last visit: {new Date(memory.lastVisit).toLocaleDateString()}</p>
</>
)}
</div>
</div>
{/* Filters */}
<div className="bg-white rounded-xl shadow-lg p-6 mb-6">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Search */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Search Conversations
</label>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search your messages..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
{/* Topic Filter */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Filter by Topic
</label>
<select
value={selectedTopic}
onChange={(e) => setSelectedTopic(e.target.value)}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
>
<option value="all">All Topics</option>
{memory?.topics.map(topic => (
<option key={topic} value={topic}>
{topic.charAt(0).toUpperCase() + topic.slice(1).replace('_', ' ')}
</option>
))}
</select>
</div>
</div>
</div>
{/* Conversations List */}
<div className="space-y-4">
{filteredConversations.length === 0 ? (
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
<p className="text-gray-600">
{searchQuery || selectedTopic !== 'all'
? 'No conversations match your filters.'
: 'No conversations yet. Go talk to Bubbe!'}
</p>
</div>
) : (
filteredConversations.map((conv, index) => (
<div key={index} className="bg-white rounded-xl shadow-lg p-6">
<div className="text-xs text-gray-500 mb-2">
{new Date(conv.timestamp).toLocaleString()}
</div>
{/* User Message */}
<div className="mb-4">
<div className="text-sm font-semibold text-gray-700 mb-1">You:</div>
<div className="bg-blue-50 rounded-lg p-3 text-gray-800">
{conv.message}
</div>
</div>
{/* Bubbe's Response */}
<div>
<div className="text-sm font-semibold text-amber-700 mb-1">Bubbe:</div>
<div className="bg-amber-50 rounded-lg p-3 text-gray-800">
{conv.response}
</div>
</div>
{/* Context/Topics */}
{conv.context && (
<div className="mt-3 text-xs text-gray-500">
Topics: {conv.context}
</div>
)}
</div>
))
)}
</div>
{/* Load More / Pagination could go here */}
{filteredConversations.length > 20 && (
<div className="mt-6 text-center">
<button className="px-6 py-2 bg-amber-500 text-white rounded-lg hover:bg-amber-600 transition-colors">
Load More Conversations
</button>
</div>
)}
{/* Export Options */}
<div className="mt-8 bg-white rounded-xl shadow-lg p-6">
<h2 className="text-xl font-semibold text-gray-800 mb-4">Export Your Data</h2>
<div className="flex gap-4">
<button
onClick={() => {
// Export as JSON
const dataStr = JSON.stringify(memory, null, 2)
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr)
const exportFileDefaultName = `bubbe_conversations_${Date.now()}.json`
const linkElement = document.createElement('a')
linkElement.setAttribute('href', dataUri)
linkElement.setAttribute('download', exportFileDefaultName)
linkElement.click()
}}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
>
Export as JSON
</button>
<button
onClick={() => {
// Export as text
let textContent = 'My Conversations with Bubbe\n\n'
filteredConversations.forEach(conv => {
textContent += `Date: ${new Date(conv.timestamp).toLocaleString()}\n`
textContent += `You: ${conv.message}\n`
textContent += `Bubbe: ${conv.response}\n\n`
textContent += '---\n\n'
})
const dataUri = 'data:text/plain;charset=utf-8,'+ encodeURIComponent(textContent)
const exportFileDefaultName = `bubbe_conversations_${Date.now()}.txt`
const linkElement = document.createElement('a')
linkElement.setAttribute('href', dataUri)
linkElement.setAttribute('download', exportFileDefaultName)
linkElement.click()
}}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
>
Export as Text
</button>
</div>
</div>
</div>
</div>
)
}