Skip to content

Commit

Permalink
fix: Update chat authentication and RLS handling
Browse files Browse the repository at this point in the history
- Implement proper Supabase authentication using createClientComponentClient
- Fix Row Level Security (RLS) policy issues
- Add proper error handling for authentication states
- Update chat operations to work with authenticated sessions
- Fix TypeScript and linting issues
  • Loading branch information
while-basic committed Dec 1, 2024
1 parent 9c8d46e commit bf13bb4
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 141 deletions.
71 changes: 48 additions & 23 deletions app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Sidebar } from "@/components/chat/sidebar"
import { useEffect, useState } from "react"
import { getConversations, createConversation, updateConversation, Conversation } from "@/lib/chat"
import { Message } from "@/components/chat/message-list"
import { cn } from "@/lib/utils"

export default function ChatPage() {
const { user, loading } = useAuth()
Expand All @@ -19,27 +20,33 @@ export default function ChatPage() {
const [conversations, setConversations] = useState<Conversation[]>([])
const [currentConversation, setCurrentConversation] = useState<Conversation | null>(null)

useEffect(() => {
if (!loading && !user) {
setShowAuthDialog(true)
}
if (user) {
loadConversations()
}
}, [user, loading])

const loadConversations = async () => {
try {
if (!user?.id) return
const data = await getConversations(user.id)
setConversations(data)
} catch (error) {
} catch (error: unknown) {
console.error('Error loading conversations:', error)
if (error instanceof Error && error.message === 'Not authenticated') {
setShowAuthDialog(true)
}
}
}

useEffect(() => {
if (!loading && !user) {
setShowAuthDialog(true)
}
if (user) {
loadConversations()
}
}, [user, loading, loadConversations])

const handleNewMessage = async (messages: Message[]) => {
if (!user?.id) return
if (!user?.id) {
setShowAuthDialog(true)
return
}

try {
if (!currentConversation) {
Expand All @@ -56,11 +63,24 @@ export default function ChatPage() {
prev.map(conv => conv.id === updatedConversation.id ? updatedConversation : conv)
)
}
} catch (error) {
} catch (error: unknown) {
console.error('Error saving conversation:', error)
if (error instanceof Error && error.message === 'Not authenticated') {
setShowAuthDialog(true)
}
}
}

const handleNewConversation = () => {
console.log('handleNewConversation called');
setCurrentConversation(null);
console.log('currentConversation set to null');
setConversations(prev => {
console.log('updating conversations');
return [...prev.filter(conv => conv.id !== undefined)];
});
}

return (
<div className="flex flex-col min-h-screen">
<div className="flex items-center justify-between px-4 py-4 border-b">
Expand All @@ -75,24 +95,29 @@ export default function ChatPage() {
<Button
variant="outline"
size="icon"
className="md:hidden"
onClick={() => setShowSidebar(!showSidebar)}
>
<Menu className="h-4 w-4" />
</Button>
</div>
<div className="flex flex-1 overflow-hidden">
{showSidebar && (
<div className="flex-shrink-0">
<Sidebar
conversations={conversations}
currentConversation={currentConversation}
onSelectConversation={setCurrentConversation}
/>
</div>
)}
<main className="flex-1 overflow-auto">
<div className="container h-full py-4">
<div className={cn(
"md:w-64 border-r bg-background",
showSidebar ? "w-64" : "hidden",
"md:block"
)}>
<Sidebar
conversations={conversations}
currentConversation={currentConversation}
onSelectConversation={setCurrentConversation}
onNewConversation={handleNewConversation}
/>
</div>
<main className="flex-1 overflow-hidden">
<div className="container max-w-4xl mx-auto h-full p-4">
<ChatInterface
key={currentConversation ? currentConversation.id : 'new'}
conversation={currentConversation}
onNewMessage={handleNewMessage}
/>
Expand Down
73 changes: 48 additions & 25 deletions components/chat/chat-interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Conversation } from '@/lib/chat'
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import { useToast } from "@/components/ui/use-toast"
import { Card, CardContent, CardFooter } from "@/components/ui/card"
import { ScrollArea } from "@/components/ui/scroll-area"
import { MessageSquare } from "lucide-react"

interface ChatInterfaceProps {
conversation: Conversation | null
Expand All @@ -16,12 +19,13 @@ export function ChatInterface({ conversation, onNewMessage }: ChatInterfaceProps
const [isLoading, setIsLoading] = useState(false)
const { toast } = useToast()

// Reset state when conversation changes
useEffect(() => {
if (conversation) {
setMessages(conversation.messages || [])
} else {
setMessages([])
}
console.log('ChatInterface: conversation changed', { conversation });
setMessages(conversation?.messages || [])
setInputValue('')
setIsLoading(false)
console.log('ChatInterface: states reset');
}, [conversation])

const handleSubmit = async (e: React.FormEvent) => {
Expand Down Expand Up @@ -54,12 +58,13 @@ export function ChatInterface({ conversation, onNewMessage }: ChatInterfaceProps
}),
})

const data = await response.json()

if (!response.ok) {
const data = await response.json()
throw new Error(data.message || 'Failed to get response')
}

const data = await response.json()

// Add AI response to messages
const assistantMessage: Message = {
role: 'assistant',
Expand All @@ -82,33 +87,51 @@ export function ChatInterface({ conversation, onNewMessage }: ChatInterfaceProps
}

return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-auto p-4">
<MessageList messages={messages} isLoading={isLoading} />
</div>
<form onSubmit={handleSubmit} className="p-4 border-t bg-background">
<div className="flex flex-col gap-2">
<Card className="flex flex-col h-[calc(100vh-8rem)] border rounded-xl shadow-lg">
<CardContent className="flex-1 p-0">
<ScrollArea className="h-full">
<div className="p-4">
{!conversation && messages.length === 0 ? (
<div className="flex flex-col items-center justify-center h-[calc(100vh-16rem)] text-center space-y-4">
<MessageSquare className="h-12 w-12 text-muted-foreground" />
<div className="space-y-2">
<h3 className="text-lg font-semibold">Start a New Chat</h3>
<p className="text-sm text-muted-foreground max-w-sm">
Begin your conversation by typing a message below.
</p>
</div>
</div>
) : (
<MessageList messages={messages} isLoading={isLoading} />
)}
</div>
</ScrollArea>
</CardContent>
<CardFooter className="p-4 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<form onSubmit={handleSubmit} className="w-full space-y-2">
<Textarea
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Type your message..."
className="min-h-[80px]"
placeholder={!conversation ? "Type your message to start a new chat..." : "Type your message..."}
className="min-h-[80px] max-h-[200px] resize-none focus-visible:ring-1"
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSubmit(e)
}
}}
/>
<Button
type="submit"
disabled={isLoading || !inputValue.trim()}
className="self-end"
>
{isLoading ? 'Sending...' : 'Send'}
</Button>
</div>
</form>
</div>
<div className="flex justify-end">
<Button
type="submit"
disabled={isLoading || !inputValue.trim()}
className="w-24"
>
{isLoading ? "Sending..." : "Send"}
</Button>
</div>
</form>
</CardFooter>
</Card>
)
}
58 changes: 33 additions & 25 deletions components/chat/message-list.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { motion } from "framer-motion"
import { Bot, User } from "lucide-react"
import { cn } from "@/lib/utils"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"

export type Message = {
role: "user" | "assistant"
Expand All @@ -14,37 +15,42 @@ interface MessageListProps {

export function MessageList({ messages, isLoading }: MessageListProps) {
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-4">
{messages.map((message, index) => (
<motion.div
key={index}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
className={cn(
"flex gap-3",
"group flex gap-3 relative",
message.role === "assistant" ? "flex-row" : "flex-row-reverse"
)}
>
<div
<Avatar className={cn(
"h-8 w-8 border shadow-sm",
message.role === "assistant"
? "bg-primary text-primary-foreground"
: "bg-muted"
)}>
<AvatarFallback>
{message.role === "assistant" ? (
<Bot className="h-4 w-4" />
) : (
<User className="h-4 w-4" />
)}
</AvatarFallback>
</Avatar>
<div
className={cn(
"flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow",
message.role === "assistant"
? "bg-primary text-primary-foreground"
: "bg-muted"
"flex-1 overflow-hidden rounded-2xl px-4 py-2 transition-colors",
message.role === "assistant"
? "bg-muted text-muted-foreground"
: "bg-primary text-primary-foreground",
"shadow-sm hover:shadow-md transition-shadow duration-200"
)}
>
{message.role === "assistant" ? (
<Bot className="h-5 w-5" />
) : (
<User className="h-5 w-5" />
)}
</div>
<div className={cn(
"flex-1 rounded-lg px-4 py-2 shadow-sm",
message.role === "assistant" ? "bg-muted" : "bg-primary text-primary-foreground"
)}>
<div className="prose prose-sm dark:prose-invert">
<div className="prose prose-sm dark:prose-invert break-words">
{message.content}
</div>
</div>
Expand All @@ -56,14 +62,16 @@ export function MessageList({ messages, isLoading }: MessageListProps) {
animate={{ opacity: 1, y: 0 }}
className="flex gap-3"
>
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border bg-primary text-primary-foreground shadow">
<Bot className="h-5 w-5" />
</div>
<div className="flex-1 space-y-2">
<Avatar className="h-8 w-8 border bg-primary text-primary-foreground shadow-sm">
<AvatarFallback>
<Bot className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<div className="flex-1 space-y-2 rounded-2xl bg-muted p-4">
<div className="flex space-x-2">
<div className="h-2 w-2 animate-bounce rounded-full bg-zinc-400 [animation-delay:-0.3s]"></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-zinc-400 [animation-delay:-0.15s]"></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-zinc-400"></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-muted-foreground/40 [animation-delay:-0.3s]"></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-muted-foreground/40 [animation-delay:-0.15s]"></div>
<div className="h-2 w-2 animate-bounce rounded-full bg-muted-foreground/40"></div>
</div>
</div>
</motion.div>
Expand Down
Loading

0 comments on commit bf13bb4

Please sign in to comment.