Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,59 @@ These client components wrap server components to trigger GA events on mount.

**Documentation**: See `docs/google-analytics-integration.md` for comprehensive setup details, metric recommendations, and troubleshooting.

## Bug Fixes & Improvements (June 2026)

### 1. Chat Auto-Scroll Fix
**Issue**: Chat automatically scrolled to bottom on every message, disrupting users reading previous messages.

**Solution**: Implemented smart scroll detection that only auto-scrolls when user is already at the bottom of the chat.

**Files Modified**:
- `app/[locale]/chat/page.tsx` - Added scroll position tracking with `messagesContainerRef` and `shouldAutoScrollRef`
- `app/chat/page.tsx` - Same smart scroll implementation
- `components/floating-chat-bubble.tsx` - Removed duplicate scroll triggers, added smart scroll logic

**How it works**:
- Tracks distance from bottom: `scrollHeight - (scrollTop + clientHeight)`
- Only auto-scrolls if within 100px of bottom
- Preserves user's scroll position when reading previous messages
- Smooth scrolling when at bottom

**Benefits**:
- ✅ Users can read previous messages without interruption
- ✅ Auto-scroll still works when at bottom
- ✅ Much better mobile experience
- ✅ No more jumping/jarring scrolls during message streaming

### 2. Duplicate Navigation Bar Fix
**Issue**: Chat page showed duplicate elements at top and bottom - main navigation bar at top, and footer appearing at bottom below chat area, cluttering the interface.

**Solution**: Created `LayoutContent` component to conditionally hide footer on chat pages while keeping the main navigation. The footer itself already contains only the attribution text (no duplicate nav items), but it appeared as clutter in the chat interface.

**Files Modified**:
- `app/layout.tsx` - Uses new `LayoutContent` component instead of directly rendering SiteHeader/Footer
- `components/layout-content.tsx` - New component that conditionally renders navigation based on route
- `components/footer.tsx` - Already optimized (shows only attribution: "Built with ❤️ by Code For Pakistan")

**How it works**:
```typescript
const isChatPage = pathname.includes('/chat')

return (
<>
<SiteHeader /> // Always shown
<div>{children}</div>
{!isChatPage && <Footer />} // Hidden only on chat pages
</>
)
```

**Benefits**:
- ✅ Clean chat interface with only top navigation
- ✅ No unnecessary footer clutter on chat page
- ✅ Footer attribution still shows on all other pages
- ✅ Focused, distraction-free chat experience

## External Services

- **Pehchan**: Pakistan's national digital identity (OAuth provider)
Expand Down
32 changes: 28 additions & 4 deletions app/[locale]/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { Suspense, useState, useRef, useEffect } from "react"
import { Suspense, useState, useRef, useEffect, useCallback } from "react"
import { useSearchParams } from "next/navigation"
import { useChat } from "ai/react"
import {
Expand Down Expand Up @@ -41,6 +41,8 @@ function ChatContent() {
const { toast } = useToast()
const messagesEndRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)
const messagesContainerRef = useRef<HTMLDivElement>(null)
const shouldAutoScrollRef = useRef(true)

const {
messages,
Expand Down Expand Up @@ -78,11 +80,29 @@ function ChatContent() {
}
}, [initialQuery, hasSubmittedInitial, append])

// Auto-scroll to latest message
// Track if user is at bottom of messages
const handleScroll = useCallback(() => {
if (!messagesContainerRef.current) return

const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current
const distanceFromBottom = scrollHeight - (scrollTop + clientHeight)

// Consider "at bottom" if within 100px threshold
shouldAutoScrollRef.current = distanceFromBottom < 100
}, [])

// Auto-scroll only if user is at bottom
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
if (shouldAutoScrollRef.current && messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
}
}, [messages])

// Scroll to bottom on initial render
useEffect(() => {
shouldAutoScrollRef.current = true
}, [])

// Focus input on mount
useEffect(() => {
if (!initialQuery) {
Expand Down Expand Up @@ -152,7 +172,11 @@ function ChatContent() {
</div>

{/* Messages Area */}
<div className="flex-1 overflow-y-auto">
<div
className="flex-1 overflow-y-auto"
ref={messagesContainerRef}
onScroll={handleScroll}
>
<div className="container max-w-3xl px-4 py-6">
<div className="flex flex-col gap-6">
{messages.map((message) => (
Expand Down
32 changes: 28 additions & 4 deletions app/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { Suspense, useState, useRef, useEffect } from "react"
import { Suspense, useState, useRef, useEffect, useCallback } from "react"
import { useSearchParams } from "next/navigation"
import { useChat } from "ai/react"
import {
Expand Down Expand Up @@ -39,6 +39,8 @@ function ChatContent() {
const { toast } = useToast()
const messagesEndRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)
const messagesContainerRef = useRef<HTMLDivElement>(null)
const shouldAutoScrollRef = useRef(true)

const {
messages,
Expand Down Expand Up @@ -76,11 +78,29 @@ function ChatContent() {
}
}, [initialQuery, hasSubmittedInitial, append])

// Auto-scroll to latest message
// Track if user is at bottom of messages
const handleScroll = useCallback(() => {
if (!messagesContainerRef.current) return

const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current
const distanceFromBottom = scrollHeight - (scrollTop + clientHeight)

// Consider "at bottom" if within 100px threshold
shouldAutoScrollRef.current = distanceFromBottom < 100
}, [])

// Auto-scroll only if user is at bottom
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
if (shouldAutoScrollRef.current && messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
}
}, [messages])

// Scroll to bottom on initial render
useEffect(() => {
shouldAutoScrollRef.current = true
}, [])

// Focus input on mount
useEffect(() => {
if (!initialQuery) {
Expand Down Expand Up @@ -151,7 +171,11 @@ function ChatContent() {
</div>

{/* Messages Area */}
<div className="flex-1 overflow-y-auto">
<div
className="flex-1 overflow-y-auto"
ref={messagesContainerRef}
onScroll={handleScroll}
>
<div className="container max-w-3xl px-4 py-6">
<div className="flex flex-col gap-6">
{messages.map((message) => (
Expand Down
5 changes: 2 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Toaster } from "@/components/ui/toaster"
import { Analytics } from "@vercel/analytics/react"
import { GoogleAnalytics } from "@/components/google-analytics"
import { FloatingChatBubble } from "@/components/floating-chat-bubble"
import { LayoutContent } from "@/components/layout-content"

export const metadata: Metadata = {
title: {
Expand Down Expand Up @@ -49,9 +50,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<div className="relative flex h-screen flex-col">
<SiteHeader />
<div className="flex min-h-0 flex-1 overflow-y-auto">{children}</div>
<Footer />
<LayoutContent>{children}</LayoutContent>
<Toaster />
<FloatingChatBubble />
</div>
Expand Down
35 changes: 30 additions & 5 deletions components/floating-chat-bubble.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { useState, useRef, useEffect } from "react"
import { useState, useRef, useEffect, useCallback } from "react"
import { usePathname } from "next/navigation"
import { useChat } from "ai/react"
import {
Expand Down Expand Up @@ -32,6 +32,8 @@ export function FloatingChatBubble() {
const [isGenerating, setIsGenerating] = useState(false)
const { toast } = useToast()
const messagesEndRef = useRef<HTMLDivElement>(null)
const messagesContainerRef = useRef<HTMLDivElement>(null)
const shouldAutoScrollRef = useRef(true)

const {
messages,
Expand All @@ -51,19 +53,38 @@ export function FloatingChatBubble() {
},
],
onResponse: (response) => {
if (response) {
if// Don't auto-scroll here; let the useEffect handle it
setIsGenerating(false)
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
}
},
onError: (error) => {
if (error) setIsGenerating(false)
},
})
// Track if user is at bottom of messages
const handleScroll = useCallback(() => {
if (!messagesContainerRef.current) return

const { scrollTop, scrollHeight, clientHeight } = messagesContainerRef.current
const distanceFromBottom = scrollHeight - (scrollTop + clientHeight)

// Consider "at bottom" if within 100px threshold
shouldAutoScrollRef.current = distanceFromBottom < 100
}, [])

// Auto-scroll only if user is at bottom and chat is open
useEffect(() => {
if (shouldAutoScrollRef.current && isOpen && messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
}
}, [messages, isOpen])

// Scroll to bottom when chat opens
useEffect(() => {
if (isOpen) {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
shouldAutoScrollRef.current = true
}
}, [dRef.current?.scrollIntoView({ behavior: "smooth" })
}
}, [messages, isOpen])

Expand Down Expand Up @@ -145,7 +166,11 @@ export function FloatingChatBubble() {
</div>

{/* Messages */}
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto p-4">
<div
className="flex min-h-0 flex-1 flex-col overflow-y-auto p-4"
ref={messagesContainerRef}
onScroll={handleScroll}
>
<div className="flex flex-col gap-4">
{messages.map((message: any) => (
<ChatBubble
Expand Down
24 changes: 24 additions & 0 deletions components/layout-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
'use client'

import { usePathname } from "next/navigation"
import { SiteHeader } from "@/components/site-header"
import { Footer } from "@/components/footer"

interface LayoutContentProps {
children: React.ReactNode
}

export function LayoutContent({ children }: LayoutContentProps) {
const pathname = usePathname()

// Hide footer only on chat pages
const isChatPage = pathname.includes('/chat')

return (
<>
<SiteHeader />
<div className="flex min-h-0 flex-1 overflow-y-auto">{children}</div>
{!isChatPage && <Footer />}
</>
)
}
Loading