← back to Dear Bubbe Nextjs
app/messagespaid/page.tsx
544 lines
'use client'
import { useState, useEffect, useRef } from 'react'
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import DatingPhotoUpload from '@/components/DatingPhotoUpload'
interface Message {
id: string
timestamp: Date
content: string
sender: 'user' | 'bubbe'
saved: boolean
incognito: boolean
hasPhoto?: boolean
photoUrl?: string
}
interface Conversation {
id: string
title: string
messages: Message[]
createdAt: Date
lastMessage: Date
saved: boolean
incognito: boolean
}
export default function MessagesPaidPage() {
const { data: session, status } = useSession()
const router = useRouter()
const messagesEndRef = useRef<HTMLDivElement>(null)
// State
const [conversations, setConversations] = useState<Conversation[]>([])
const [currentConversation, setCurrentConversation] = useState<Conversation | null>(null)
const [input, setInput] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [incognitoMode, setIncognitoMode] = useState(false)
const [showPhotoUpload, setShowPhotoUpload] = useState(false)
const [smsEnabled, setSmsEnabled] = useState(false)
const [phoneNumber, setPhoneNumber] = useState('')
const [showSettings, setShowSettings] = useState(false)
// Redirect if not logged in
useEffect(() => {
if (status === 'unauthenticated') {
router.push('/')
}
}, [status, router])
// Load saved conversations
useEffect(() => {
if (session?.user?.email) {
loadConversations()
loadSmsSettings()
}
}, [session])
// Auto-scroll to bottom
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [currentConversation?.messages])
const loadConversations = async () => {
try {
const savedConvos = localStorage.getItem(`bubbe_dm_${session?.user?.email}`)
if (savedConvos) {
const parsed = JSON.parse(savedConvos)
// Filter out incognito conversations that weren't saved
const filtered = parsed.filter((c: Conversation) => c.saved || !c.incognito)
setConversations(filtered)
}
} catch (error) {
console.error('Failed to load conversations:', error)
}
}
const loadSmsSettings = async () => {
try {
const settings = localStorage.getItem(`bubbe_sms_${session?.user?.email}`)
if (settings) {
const { enabled, phone } = JSON.parse(settings)
setSmsEnabled(enabled)
setPhoneNumber(phone)
}
} catch (error) {
console.error('Failed to load SMS settings:', error)
}
}
const saveSmsSettings = () => {
localStorage.setItem(`bubbe_sms_${session?.user?.email}`, JSON.stringify({
enabled: smsEnabled,
phone: phoneNumber
}))
setShowSettings(false)
}
const startNewConversation = () => {
const newConvo: Conversation = {
id: Date.now().toString(),
title: `Chat ${new Date().toLocaleDateString()}`,
messages: [],
createdAt: new Date(),
lastMessage: new Date(),
saved: !incognitoMode,
incognito: incognitoMode
}
if (!incognitoMode) {
setConversations([...conversations, newConvo])
saveConversations([...conversations, newConvo])
}
setCurrentConversation(newConvo)
}
const saveConversations = (convos: Conversation[]) => {
const toSave = convos.filter(c => c.saved)
localStorage.setItem(`bubbe_dm_${session?.user?.email}`, JSON.stringify(toSave))
}
const sendMessage = async () => {
if (!input.trim() || !currentConversation) return
const userMessage: Message = {
id: Date.now().toString(),
timestamp: new Date(),
content: input.trim(),
sender: 'user',
saved: !incognitoMode,
incognito: incognitoMode
}
// Add user message
const updatedMessages = [...currentConversation.messages, userMessage]
const updatedConvo = {
...currentConversation,
messages: updatedMessages,
lastMessage: new Date()
}
setCurrentConversation(updatedConvo)
setInput('')
setIsLoading(true)
try {
// Send to API
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: input,
userId: session?.user?.email,
mode: 'bubbe',
incognito: incognitoMode,
userProfile: {
preferredName: session?.user?.name,
incognito: incognitoMode
}
})
})
if (response.ok) {
const data = await response.json()
const bubbeMessage: Message = {
id: (Date.now() + 1).toString(),
timestamp: new Date(),
content: data.response,
sender: 'bubbe',
saved: !incognitoMode,
incognito: incognitoMode
}
// Add Bubbe's response
const finalMessages = [...updatedMessages, bubbeMessage]
const finalConvo = {
...updatedConvo,
messages: finalMessages
}
setCurrentConversation(finalConvo)
// Update saved conversations if not incognito
if (!incognitoMode) {
const updatedConvos = conversations.map(c =>
c.id === finalConvo.id ? finalConvo : c
)
setConversations(updatedConvos)
saveConversations(updatedConvos)
}
// Send SMS if enabled
if (smsEnabled && phoneNumber) {
sendSmsNotification(data.response)
}
}
} catch (error) {
console.error('Failed to send message:', error)
} finally {
setIsLoading(false)
}
}
const sendSmsNotification = async (message: string) => {
try {
await fetch('/api/sms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
phone: phoneNumber,
message: `Bubbe says: ${message.substring(0, 140)}...`
})
})
} catch (error) {
console.error('Failed to send SMS:', error)
}
}
const toggleSaveConversation = () => {
if (!currentConversation) return
const updated = {
...currentConversation,
saved: !currentConversation.saved
}
setCurrentConversation(updated)
if (updated.saved) {
// Add to saved conversations
const exists = conversations.find(c => c.id === updated.id)
if (!exists) {
const newConvos = [...conversations, updated]
setConversations(newConvos)
saveConversations(newConvos)
}
} else {
// Remove from saved conversations
const filtered = conversations.filter(c => c.id !== updated.id)
setConversations(filtered)
saveConversations(filtered)
}
}
const deleteConversation = (id: string) => {
const filtered = conversations.filter(c => c.id !== id)
setConversations(filtered)
saveConversations(filtered)
if (currentConversation?.id === id) {
setCurrentConversation(null)
}
}
const handlePhotoUpload = (response: any) => {
setShowPhotoUpload(false)
// Add the response as a message
if (response.bubbeResponse && currentConversation) {
const photoMessage: Message = {
id: Date.now().toString(),
timestamp: new Date(),
content: response.bubbeResponse,
sender: 'bubbe',
saved: !incognitoMode,
incognito: incognitoMode,
hasPhoto: true,
photoUrl: response.filename
}
const updatedMessages = [...currentConversation.messages, photoMessage]
const updatedConvo = {
...currentConversation,
messages: updatedMessages
}
setCurrentConversation(updatedConvo)
}
}
if (status === 'loading') {
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...</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-amber-50 to-orange-100">
<div className="flex h-screen">
{/* Sidebar */}
<div className="w-80 bg-white shadow-lg flex flex-col">
{/* Header */}
<div className="p-4 border-b">
<Link href="/" className="text-amber-600 hover:text-amber-700 text-sm">
← Back to Bubbe
</Link>
<h2 className="text-xl font-bold text-gray-800 mt-2">Private Messages</h2>
{/* Incognito Toggle */}
<label className="flex items-center mt-3 cursor-pointer">
<input
type="checkbox"
checked={incognitoMode}
onChange={(e) => setIncognitoMode(e.target.checked)}
className="mr-2"
/>
<span className="text-sm text-gray-600">
🕵️ Incognito Mode (not saved)
</span>
</label>
</div>
{/* Conversations List */}
<div className="flex-1 overflow-y-auto p-4 space-y-2">
<button
onClick={startNewConversation}
className="w-full px-4 py-2 bg-amber-500 text-white rounded-lg hover:bg-amber-600 transition-colors"
>
+ New Conversation
</button>
{conversations.map((convo) => (
<div
key={convo.id}
onClick={() => setCurrentConversation(convo)}
className={`p-3 rounded-lg cursor-pointer transition-colors ${
currentConversation?.id === convo.id
? 'bg-amber-100 border-amber-500 border'
: 'bg-gray-50 hover:bg-gray-100'
}`}
>
<div className="flex justify-between items-start">
<div>
<div className="font-medium text-gray-800">{convo.title}</div>
<div className="text-xs text-gray-500">
{new Date(convo.lastMessage).toLocaleDateString()}
</div>
</div>
<button
onClick={(e) => {
e.stopPropagation()
deleteConversation(convo.id)
}}
className="text-red-500 hover:text-red-700"
>
×
</button>
</div>
</div>
))}
</div>
{/* Settings */}
<div className="p-4 border-t">
<button
onClick={() => setShowSettings(!showSettings)}
className="w-full px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
>
⚙️ SMS Settings
</button>
</div>
</div>
{/* Main Chat Area */}
<div className="flex-1 flex flex-col">
{currentConversation ? (
<>
{/* Chat Header */}
<div className="bg-white shadow-sm p-4 flex justify-between items-center">
<div>
<h3 className="text-lg font-semibold text-gray-800">
{currentConversation.title}
</h3>
{currentConversation.incognito && (
<span className="text-xs text-gray-500">🕵️ Incognito - Not Saved</span>
)}
</div>
<div className="flex gap-2">
<button
onClick={() => setShowPhotoUpload(!showPhotoUpload)}
className="px-3 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
>
📸 Upload Photo
</button>
<button
onClick={toggleSaveConversation}
className={`px-3 py-1 rounded ${
currentConversation.saved
? 'bg-green-500 text-white hover:bg-green-600'
: 'bg-gray-300 text-gray-700 hover:bg-gray-400'
}`}
>
{currentConversation.saved ? '💾 Saved' : '💾 Save'}
</button>
</div>
</div>
{/* Photo Upload Section */}
{showPhotoUpload && (
<div className="p-4 bg-white border-b">
<DatingPhotoUpload
userId={session?.user?.email || ''}
onUploadComplete={handlePhotoUpload}
/>
</div>
)}
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4 space-y-3 bg-gray-50">
{currentConversation.messages.map((msg) => (
<div
key={msg.id}
className={`flex ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className={`max-w-2xl p-3 rounded-lg ${
msg.sender === 'user'
? 'bg-blue-500 text-white'
: 'bg-white text-gray-800 shadow'
}`}
>
{msg.hasPhoto && (
<div className="text-xs mb-1 opacity-75">📸 Photo shared</div>
)}
<p className="whitespace-pre-wrap">{msg.content}</p>
<div className="text-xs opacity-70 mt-1">
{new Date(msg.timestamp).toLocaleTimeString()}
</div>
</div>
</div>
))}
{isLoading && (
<div className="flex justify-start">
<div className="bg-white text-gray-500 p-3 rounded-lg shadow">
Bubbe is typing...
</div>
</div>
)}
<div ref={messagesEndRef} />
</div>
{/* Input */}
<div className="bg-white p-4 border-t">
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder={incognitoMode ? "Message Bubbe privately..." : "Message Bubbe..."}
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
disabled={isLoading}
/>
<button
onClick={sendMessage}
disabled={!input.trim() || isLoading}
className="px-6 py-2 bg-amber-500 text-white rounded-lg hover:bg-amber-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Send
</button>
</div>
{incognitoMode && (
<p className="text-xs text-gray-500 mt-2">
🕵️ This conversation won't be saved unless you click Save
</p>
)}
</div>
</>
) : (
<div className="flex-1 flex items-center justify-center">
<div className="text-center">
<h3 className="text-2xl font-bold text-gray-700 mb-4">
Welcome to Bubbe DMs! 💌
</h3>
<p className="text-gray-600 mb-6">
Have private conversations with Bubbe. Choose to save or delete anything.
</p>
<button
onClick={startNewConversation}
className="px-6 py-3 bg-amber-500 text-white rounded-lg hover:bg-amber-600 transition-colors"
>
Start New Conversation
</button>
</div>
</div>
)}
</div>
</div>
{/* SMS Settings Modal */}
{showSettings && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full">
<h3 className="text-xl font-bold text-gray-800 mb-4">SMS Settings</h3>
<label className="flex items-center mb-4">
<input
type="checkbox"
checked={smsEnabled}
onChange={(e) => setSmsEnabled(e.target.checked)}
className="mr-2"
/>
<span>Enable SMS notifications from Bubbe</span>
</label>
{smsEnabled && (
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Phone Number
</label>
<input
type="tel"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value)}
placeholder="+1234567890"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-amber-500"
/>
</div>
)}
<div className="flex gap-2">
<button
onClick={saveSmsSettings}
className="flex-1 px-4 py-2 bg-amber-500 text-white rounded-lg hover:bg-amber-600"
>
Save
</button>
<button
onClick={() => setShowSettings(false)}
className="flex-1 px-4 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400"
>
Cancel
</button>
</div>
</div>
</div>
)}
</div>
)
}