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
52 changes: 30 additions & 22 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import { SpeedInsights } from "@vercel/speed-insights/next"
import { Toaster } from '@/components/ui/sonner'
import { MapToggleProvider } from '@/components/map-toggle-context'
import { ProfileToggleProvider } from '@/components/profile-toggle-context'
import { UsageToggleProvider } from '@/components/usage-toggle-context'
import { CalendarToggleProvider } from '@/components/calendar-toggle-context'
import { HistoryToggleProvider } from '@/components/history-toggle-context'
import { HistorySidebar } from '@/components/history-sidebar'
import { MapLoadingProvider } from '@/components/map-loading-context';
import ConditionalLottie from '@/components/conditional-lottie';
import { MapProvider as MapContextProvider } from '@/components/map/map-context'
Expand Down Expand Up @@ -70,28 +73,33 @@ export default function RootLayout({
)}
>
<CalendarToggleProvider>
<MapToggleProvider>
<ProfileToggleProvider>
<ThemeProvider
attribute="class"
defaultTheme="earth"
enableSystem
disableTransitionOnChange
themes={['light', 'dark', 'earth']}
>
<MapContextProvider>
<MapLoadingProvider>
<Header />
<ConditionalLottie />
{children}
<Sidebar />
<Footer />
<Toaster />
</MapLoadingProvider>
</MapContextProvider>
</ThemeProvider>
</ProfileToggleProvider>
</MapToggleProvider>
<HistoryToggleProvider>
<MapToggleProvider>
<ProfileToggleProvider>
<UsageToggleProvider>
<ThemeProvider
attribute="class"
defaultTheme="earth"
enableSystem
disableTransitionOnChange
themes={['light', 'dark', 'earth']}
>
<MapContextProvider>
<MapLoadingProvider>
<Header />
<ConditionalLottie />
{children}
<Sidebar />
<HistorySidebar />
<Footer />
<Toaster />
</MapLoadingProvider>
</MapContextProvider>
</ThemeProvider>
</UsageToggleProvider>
</ProfileToggleProvider>
</MapToggleProvider>
</HistoryToggleProvider>
</CalendarToggleProvider>
<Analytics />
<SpeedInsights />
Expand Down
7 changes: 5 additions & 2 deletions components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import { MapProvider } from './map/map-provider'
import { useUIState, useAIState } from 'ai/rsc'
import MobileIconsBar from './mobile-icons-bar'
import { useProfileToggle, ProfileToggleEnum } from "@/components/profile-toggle-context";
import { useUsageToggle } from "@/components/usage-toggle-context";
import SettingsView from "@/components/settings/settings-view";
import { UsageView } from "@/components/usage-view";
import { MapDataProvider, useMapData } from './map/map-data-context'; // Add this and useMapData
import { updateDrawingContext } from '@/lib/actions/chat'; // Import the server action
import dynamic from 'next/dynamic'
Expand All @@ -31,6 +33,7 @@ export function Chat({ id }: ChatProps) {
const [aiState] = useAIState()
const [isMobile, setIsMobile] = useState(false)
const { activeView } = useProfileToggle();
const { isUsageOpen } = useUsageToggle();
const { isCalendarOpen } = useCalendarToggle()
const [input, setInput] = useState('')
const [showEmptyScreen, setShowEmptyScreen] = useState(false)
Expand Down Expand Up @@ -107,7 +110,7 @@ export function Chat({ id }: ChatProps) {
<HeaderSearchButton />
<div className="mobile-layout-container">
<div className="mobile-map-section">
{activeView ? <SettingsView /> : <MapProvider />}
{isUsageOpen ? <UsageView /> : activeView ? <SettingsView /> : <MapProvider />}
</div>
<div className="mobile-icons-bar">
<MobileIconsBar onAttachmentClick={handleAttachment} onSubmitClick={handleMobileSubmit} />
Expand Down Expand Up @@ -218,7 +221,7 @@ export function Chat({ id }: ChatProps) {
className="w-1/2 p-4 fixed h-[calc(100vh-0.5in)] top-0 right-0 mt-[0.5in]"
style={{ zIndex: 10 }} // Added z-index
>
{activeView ? <SettingsView /> : <MapProvider />}
{isUsageOpen ? <UsageView /> : activeView ? <SettingsView /> : <MapProvider />}
</div>
</div>
</MapDataProvider>
Expand Down
6 changes: 4 additions & 2 deletions components/conditional-lottie.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import LottiePlayer from '@/components/ui/lottie-player';
import { useMapLoading } from '@/components/map-loading-context';
import { useProfileToggle } from '@/components/profile-toggle-context'; // Added import
import { useUsageToggle } from '@/components/usage-toggle-context';

const ConditionalLottie = () => {
const { isMapLoaded } = useMapLoading();
const { activeView } = useProfileToggle(); // Added this line
const { isUsageOpen } = useUsageToggle();

// Updated isVisible logic
return <LottiePlayer isVisible={!isMapLoaded && activeView === null} />;
// Updated isVisible logic to hide lottie when settings or usage is open
return <LottiePlayer isVisible={!isMapLoaded && activeView === null && !isUsageOpen} />;
};

export default ConditionalLottie;
27 changes: 19 additions & 8 deletions components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,25 @@ import {
import { MapToggle } from './map-toggle'
import { ProfileToggle } from './profile-toggle'
import { PurchaseCreditsPopup } from './purchase-credits-popup'
import { UsageSidebar } from './usage-sidebar'
import { useUsageToggle } from './usage-toggle-context'
import { useProfileToggle } from './profile-toggle-context'
import { useHistoryToggle } from './history-toggle-context'
import { useState, useEffect } from 'react'

export const Header = () => {
const { toggleCalendar } = useCalendarToggle()
const [isPurchaseOpen, setIsPurchaseOpen] = useState(false)
const [isUsageOpen, setIsUsageOpen] = useState(false)
const { toggleUsage, isUsageOpen } = useUsageToggle()
const { activeView, closeProfileView } = useProfileToggle()
const { toggleHistory } = useHistoryToggle()

const handleUsageToggle = () => {
// If we're about to open usage and profile is open, close profile first
if (!isUsageOpen && activeView) {
closeProfileView()
}
toggleUsage()
}
Comment on lines +27 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleUsageToggle closes the profile view when opening usage, but the inverse coordination is handled elsewhere (ProfileToggle closes usage when opening a profile section). History is now also a global overlay, but it isn't coordinated with either usage or profile. This will likely allow multiple overlays to be open at once (history sheet + usage view), which can produce confusing input focus/scroll behavior.

Suggestion

Centralize overlay coordination so only one of {history, usage, profile} can be open at a time. A minimal improvement is to close history when opening usage and close usage when opening history.

Example (in handleUsageToggle):

if (!isUsageOpen) {
  closeProfileView()
  setHistoryOpen(false) // or a `closeHistory()` helper
}
toggleUsage()

You may want to add closeHistory() to HistoryToggleContextType for symmetry. Reply with "@CharlieHelps yes please" if you'd like me to add a commit implementing this coordination.


useEffect(() => {
// Open payment popup as soon as application opens
Expand All @@ -32,16 +44,15 @@ export const Header = () => {
return (
<>
<PurchaseCreditsPopup isOpen={isPurchaseOpen} onClose={() => setIsPurchaseOpen(false)} />
<UsageSidebar isOpen={isUsageOpen} onClose={() => setIsUsageOpen(false)} />
<header className="fixed w-full p-1 md:p-2 flex justify-between items-center z-20 backdrop-blur bg-background/95 border-b border-border/40">
<header className="fixed w-full p-1 md:p-2 flex justify-between items-center z-[60] backdrop-blur bg-background/95 border-b border-border/40">
<div>
<a href="/">
<span className="sr-only">Chat</span>
</a>
</div>

<div className="absolute left-1 flex items-center">
<Button variant="ghost" size="icon">
<Button variant="ghost" size="icon" onClick={toggleHistory} data-testid="logo-history-toggle">
<Image
src="/images/logo.svg"
alt="Logo"
Expand All @@ -67,7 +78,7 @@ export const Header = () => {

<div id="header-search-portal" />

<Button variant="ghost" size="icon" onClick={() => setIsUsageOpen(true)}>
<Button variant="ghost" size="icon" onClick={handleUsageToggle}>
<TentTree className="h-[1.2rem] w-[1.2rem]" />
</Button>

Expand All @@ -79,7 +90,7 @@ export const Header = () => {
{/* Mobile menu buttons */}
<div className="flex md:hidden gap-2">

<Button variant="ghost" size="icon" onClick={() => setIsUsageOpen(true)}>
<Button variant="ghost" size="icon" onClick={handleUsageToggle}>
<TentTree className="h-[1.2rem] w-[1.2rem]" />
</Button>
<ProfileToggle/>
Expand All @@ -89,4 +100,4 @@ export const Header = () => {
)
}

export default Header
export default Header
35 changes: 35 additions & 0 deletions components/history-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use client'

import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
} from '@/components/ui/sheet'
import { History as HistoryIcon } from 'lucide-react'
import { ChatHistoryClient } from './sidebar/chat-history-client'
import { Suspense } from 'react'
import { HistorySkeleton } from './history-skelton'
import { useHistoryToggle } from './history-toggle-context'

export function HistorySidebar() {
const { isHistoryOpen, setHistoryOpen } = useHistoryToggle()

return (
<Sheet open={isHistoryOpen} onOpenChange={setHistoryOpen}>
<SheetContent className="w-64 rounded-tl-xl rounded-bl-xl" data-testid="history-panel">
<SheetHeader>
Comment on lines +15 to +21

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<Sheet open={isHistoryOpen} onOpenChange={setHistoryOpen}> passes setHistoryOpen directly as the handler. Many sheet/dialog components call onOpenChange(open: boolean) but some call with additional args or an event-like payload. Passing a raw setter is brittle and makes it harder to add side-effects later (e.g., closing other overlays when history opens).

Suggestion

Wrap the handler to lock the contract to a boolean and to give yourself a single place to coordinate overlays later.

<Sheet
  open={isHistoryOpen}
  onOpenChange={(open) => setHistoryOpen(open)}
>

If you want coordination (recommended), this is also where you can close Usage / Profile when open === true. Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.

<SheetTitle className="flex items-center gap-1 text-sm font-normal mb-2">
<HistoryIcon size={14} />
History
</SheetTitle>
</SheetHeader>
<div className="my-2 h-full pb-12 md:pb-10">
<Suspense fallback={<HistorySkeleton />}>
<ChatHistoryClient />
</Suspense>
</div>
</SheetContent>
</Sheet>
)
}
30 changes: 30 additions & 0 deletions components/history-toggle-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client'

import { createContext, useContext, useState, ReactNode } from "react"

interface HistoryToggleContextType {
isHistoryOpen: boolean
toggleHistory: () => void
setHistoryOpen: (open: boolean) => void
}

const HistoryToggleContext = createContext<HistoryToggleContextType | undefined>(undefined)

export const HistoryToggleProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const [isHistoryOpen, setIsHistoryOpen] = useState(false)

const toggleHistory = () => setIsHistoryOpen(prev => !prev)
const setHistoryOpen = (open: boolean) => setIsHistoryOpen(open)

return (
<HistoryToggleContext.Provider value={{ isHistoryOpen, toggleHistory, setHistoryOpen }}>
{children}
</HistoryToggleContext.Provider>
)
}

export const useHistoryToggle = () => {
const context = useContext(HistoryToggleContext)
if (!context) throw new Error('useHistoryToggle must be used within HistoryToggleProvider')
return context
}
54 changes: 16 additions & 38 deletions components/history.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,28 @@
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger
} from '@/components/ui/sheet'
'use client'

import { Button } from '@/components/ui/button'
import { ChevronLeft, Menu } from 'lucide-react'
import { cn } from '@/lib/utils'
import { History as HistoryIcon } from 'lucide-react'
import { ChatHistoryClient } from './sidebar/chat-history-client' // Updated import
import { Suspense } from 'react'
import { HistorySkeleton } from './history-skelton'
import { useHistoryToggle } from './history-toggle-context'

type HistoryProps = {
location: 'sidebar' | 'header'
}

export function History({ location }: HistoryProps) {
const { toggleHistory } = useHistoryToggle()

return (
<Sheet>
<SheetTrigger asChild>
<Button
variant="ghost"
size="icon"
className={cn({
'rounded-full text-foreground/30': location === 'sidebar'
})}
data-testid="history-button"
>
{location === 'header' ? <Menu /> : <ChevronLeft size={16} />}
</Button>
</SheetTrigger>
<SheetContent className="w-64 rounded-tl-xl rounded-bl-xl" data-testid="history-panel">
<SheetHeader>
<SheetTitle className="flex items-center gap-1 text-sm font-normal mb-2">
<HistoryIcon size={14} />
History
</SheetTitle>
</SheetHeader>
<div className="my-2 h-full pb-12 md:pb-10">
<Suspense fallback={<HistorySkeleton />}>
<ChatHistoryClient />
</Suspense>
</div>
</SheetContent>
</Sheet>
<Button
variant="ghost"
size="icon"
className={cn({
'rounded-full text-foreground/30': location === 'sidebar'
})}
data-testid="history-button"
onClick={toggleHistory}
>
{location === 'header' ? <Menu /> : <ChevronLeft size={16} />}
</Button>
)
}
7 changes: 6 additions & 1 deletion components/profile-toggle-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export enum ProfileToggleEnum {
interface ProfileToggleContextType {
activeView: ProfileToggleEnum | null
toggleProfileSection: (section: ProfileToggleEnum) => void
closeProfileView: () => void
}

const ProfileToggleContext = createContext<ProfileToggleContextType | undefined>(undefined)
Expand All @@ -28,8 +29,12 @@ export const ProfileToggleProvider: React.FC<ProfileToggleProviderProps> = ({ ch
setActiveView(prevView => (prevView === section ? null : section))
}

const closeProfileView = () => {
setActiveView(null)
}

return (
<ProfileToggleContext.Provider value={{ activeView, toggleProfileSection }}>
<ProfileToggleContext.Provider value={{ activeView, toggleProfileSection, closeProfileView }}>
{children}
</ProfileToggleContext.Provider>
)
Expand Down
18 changes: 12 additions & 6 deletions components/profile-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { User, Settings, Paintbrush, Shield, CircleUserRound } from "lucide-reac
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
import { Button } from "@/components/ui/button"
import { ProfileToggleEnum, useProfileToggle } from "./profile-toggle-context"
import { useUsageToggle } from "./usage-toggle-context"

export function ProfileToggle() {
const { toggleProfileSection } = useProfileToggle()
const { toggleProfileSection, activeView } = useProfileToggle()
const { isUsageOpen, closeUsage } = useUsageToggle()
const [alignValue, setAlignValue] = useState<'start' | 'end'>("end")
const [isMobile, setIsMobile] = useState(false)

Expand All @@ -32,7 +34,11 @@ export function ProfileToggle() {
return () => window.removeEventListener("resize", debouncedResize)
}, [])

const handleSectionChange = (section: ProfileToggleEnum) => {
const handleSectionToggle = (section: ProfileToggleEnum) => {
// If we're about to open a profile section and usage is open, close usage first
if (activeView !== section && isUsageOpen) {
closeUsage()
}
toggleProfileSection(section)
}

Expand All @@ -54,19 +60,19 @@ export function ProfileToggle() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align={alignValue} forceMount>
<DropdownMenuItem onClick={() => handleSectionChange(ProfileToggleEnum.Account)} data-testid="profile-account">
<DropdownMenuItem onClick={() => handleSectionToggle(ProfileToggleEnum.Account)} data-testid="profile-account">
<User className="mr-2 h-4 w-4" />
<span>Account</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSectionChange(ProfileToggleEnum.Settings)} data-testid="profile-settings">
<DropdownMenuItem onClick={() => handleSectionToggle(ProfileToggleEnum.Settings)} data-testid="profile-settings">
<Settings className="mr-2 h-4 w-4" />
<span>Settings</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSectionChange(ProfileToggleEnum.Appearance)} data-testid="profile-appearance">
<DropdownMenuItem onClick={() => handleSectionToggle(ProfileToggleEnum.Appearance)} data-testid="profile-appearance">
<Paintbrush className="mr-2 h-4 w-4" />
<span>Appearance</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => handleSectionChange(ProfileToggleEnum.Security)} data-testid="profile-security">
<DropdownMenuItem onClick={() => handleSectionToggle(ProfileToggleEnum.Security)} data-testid="profile-security">
<Shield className="mr-2 h-4 w-4" />
<span>Security</span>
</DropdownMenuItem>
Expand Down
Loading