Skip to content
Open
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
355 changes: 176 additions & 179 deletions bun.lock

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { UsageView } from "@/components/usage-view";
import { MapDataProvider, useMapData } from './map/map-data-context';
import { updateDrawingContext } from '@/lib/actions/chat';
import { HeaderSearchButton } from './header-search-button'
import { useOnboardingTour } from './onboarding-tour'

type ChatProps = {
id?: string // This is the chatId
Expand All @@ -39,6 +40,7 @@ export function Chat({ id }: ChatProps) {
const [isSubmitting, setIsSubmitting] = useState(false)
const [suggestions, setSuggestions] = useState<PartialRelated | null>(null)
const chatPanelRef = useRef<ChatPanelRef>(null);
const { startTour } = useOnboardingTour()

const handleAttachment = () => {
chatPanelRef.current?.handleAttachmentClick();
Expand Down Expand Up @@ -102,6 +104,29 @@ export function Chat({ id }: ChatProps) {
}
}, [id, mapData.drawnFeatures, mapData.cameraState]);

// Automatic onboarding tour trigger
useEffect(() => {
// Only trigger on the main landing page (no id) and when there are no messages
if (id || messages.length > 0) return

let timer: ReturnType<typeof setTimeout>
try {
const tourCompleted = localStorage.getItem('qcx_onboarding_v1')
if (!tourCompleted) {
timer = setTimeout(() => {
console.log("Starting onboarding tour...")
startTour(isMobile)
}, 5000) // Increased delay to ensure components are mounted
}
} catch (e) {
console.error("LocalStorage access failed", e)
}

return () => {
if (timer) clearTimeout(timer)
}
}, [id, isMobile, startTour, messages.length])

const renderSuggestions = () => {
if (suggestions) {
return (
Expand Down
4 changes: 2 additions & 2 deletions components/header-search-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export function HeaderSearchButton() {
size="icon"
onClick={handleResolutionSearch}
disabled={isAnalyzing || !map || !actions}
title="Analyze current map view"
title="Analyze current map view" data-testid="resolution-search"
>
{isAnalyzing ? (
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-current"></div>
Expand All @@ -164,7 +164,7 @@ export function HeaderSearchButton() {
)

const mobileButton = (
<Button variant="ghost" size="sm" onClick={handleResolutionSearch} disabled={isAnalyzing || !map || !actions}>
<Button variant="ghost" size="sm" onClick={handleResolutionSearch} data-testid="mobile-search-button" disabled={isAnalyzing || !map || !actions}>
<Search className="h-4 w-4 mr-2" />
Search
</Button>
Expand Down
26 changes: 14 additions & 12 deletions components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ import {
Map,
CalendarDays,
TentTree,
ChevronRight
ChevronRight,
HelpCircle
} from 'lucide-react'
import { MapToggle } from './map-toggle'
import { ProfileToggle } from './profile-toggle'
import { PurchaseCreditsPopup } from './credits/purchase-credits-popup'
import { useUsageToggle } from './usage-toggle-context'
import { useProfileToggle } from './profile-toggle-context'
import { useHistoryToggle } from './history-toggle-context'
import { useOnboardingTour } from './onboarding-tour'
import { useState, useEffect } from 'react'

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

const handleUsageToggle = () => {
if (!isUsageOpen && activeView) {
Expand All @@ -36,25 +37,27 @@ export const Header = () => {
toggleUsage()
}

useEffect(() => {
setIsPurchaseOpen(true)
}, [])
const handleStartTour = () => {
const isMobile = window.innerWidth < 768
startTour(isMobile)
}

return (
<>
<PurchaseCreditsPopup />
<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 className="flex-1 hidden md:flex justify-start gap-10 items-center z-10 pl-4">
<ProfileToggle/>
<MapToggle />
<Button variant="ghost" size="icon" onClick={toggleCalendar} title="Open Calendar" data-testid="calendar-toggle">
<CalendarDays className="h-[1.2rem] w-[1.2rem]" />
</Button>
<div id="header-search-portal" className="contents" />
<Button variant="ghost" size="icon" onClick={handleUsageToggle}>
<div id="header-search-portal" className="contents" data-testid="header-search-portal" />
<Button variant="ghost" size="icon" onClick={handleUsageToggle} data-testid="usage-toggle">
<TentTree className="h-[1.2rem] w-[1.2rem]" />
</Button>
<ModeToggle />
<Button variant="ghost" size="icon" onClick={handleStartTour} title="Help Tour" data-testid="help-tour">
<HelpCircle className="h-[1.2rem] w-[1.2rem]" />
</Button>
<HistoryContainer location="header" />
</div>

Expand All @@ -78,13 +81,12 @@ export const Header = () => {

{/* Mobile menu buttons (left side for mobile since logo is right) */}
<div className="flex md:hidden gap-2">
<Button variant="ghost" size="icon" onClick={handleUsageToggle}>
<Button variant="ghost" size="icon" onClick={handleUsageToggle} data-testid="mobile-usage-button">
<TentTree className="h-[1.2rem] w-[1.2rem]" />
</Button>
<ProfileToggle/>
</div>
</header>
</>
)
}

Expand Down
2 changes: 1 addition & 1 deletion components/map/mapbox-map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ export const Mapbox: React.FC<{ position?: { latitude: number; longitude: number
return (
<div className="relative h-full w-full">
<div
ref={mapContainer}
ref={mapContainer} data-testid="mapbox-container"
className="h-full w-full overflow-hidden rounded-l-lg"
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
Expand Down
22 changes: 16 additions & 6 deletions components/mobile-icons-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ import {
TentTree,
Paperclip,
ArrowRight,
Plus
Plus,
HelpCircle
} from 'lucide-react'
import { History } from '@/components/history'
import { MapToggle } from './map-toggle'
import { ModeToggle } from './mode-toggle'
import { ProfileToggle } from './profile-toggle'
import { useCalendarToggle } from './calendar-toggle-context'
import { useOnboardingTour } from './onboarding-tour'
import { useUsageToggle } from './usage-toggle-context'

interface MobileIconsBarProps {
onAttachmentClick: () => void;
Expand All @@ -29,12 +32,18 @@ export const MobileIconsBar: React.FC<MobileIconsBarProps> = ({ onAttachmentClic
const [, setMessages] = useUIState<typeof AI>()
const { clearChat } = useActions()
const { toggleCalendar } = useCalendarToggle()
const { startTour } = useOnboardingTour()
const { toggleUsage } = useUsageToggle()

const handleNewChat = async () => {
setMessages([])
await clearChat()
}

const handleStartTour = () => {
startTour(true)
}

return (
<div className="mobile-icons-bar-content">
<Button variant="ghost" size="icon" onClick={handleNewChat} data-testid="mobile-new-chat-button">
Expand All @@ -48,17 +57,18 @@ export const MobileIconsBar: React.FC<MobileIconsBarProps> = ({ onAttachmentClic
<Button variant="ghost" size="icon" data-testid="mobile-search-button">
<Search className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<a href="https://buy.stripe.com/14A3cv7K72TR3go14Nasg02" target="_blank" rel="noopener noreferrer">
<Button variant="ghost" size="icon">
<TentTree className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
</a>
<Button variant="ghost" size="icon" onClick={toggleUsage} data-testid="mobile-usage-button">
<TentTree className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<Button variant="ghost" size="icon" onClick={onAttachmentClick} data-testid="mobile-attachment-button">
<Paperclip className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<Button variant="ghost" size="icon" data-testid="mobile-submit-button" onClick={onSubmitClick}>
<ArrowRight className="h-[1.2rem] w-[1.2rem] transition-all rotate-0 scale-100" />
</Button>
<Button variant="ghost" size="icon" onClick={handleStartTour} data-testid="mobile-help-tour">
<HelpCircle className="h-[1.2rem] w-[1.2rem]" />
</Button>
<History location="header" />
<ModeToggle />
</div>
Expand Down
Loading