From e9204d58083bcfd7139dfa50f2f2873b9501a5e0 Mon Sep 17 00:00:00 2001 From: DhanNarula Date: Sat, 10 Jan 2026 16:40:30 -0500 Subject: [PATCH 1/2] Enhance IntersectionSidebar and PersonaSidebar with animations and state management - Added `react-photo-view` dependency for improved image handling. - Implemented tab state management in IntersectionSidebar to reset to the overview tab on hotspot change. - Enhanced PersonaSidebar with animated transitions for persona changes and message display. - Updated Next.js configuration to remove unnecessary turbopack settings. --- bun.lock | 3 + components/sidebar/intersection-sidebar.tsx | 108 ++++++++++--- components/sidebar/persona-sidebar.tsx | 166 ++++++++++++++------ next.config.ts | 6 +- 4 files changed, 209 insertions(+), 74 deletions(-) diff --git a/bun.lock b/bun.lock index 1018d63..9c0e908 100644 --- a/bun.lock +++ b/bun.lock @@ -20,6 +20,7 @@ "react": "19.2.3", "react-dom": "19.2.3", "react-map-gl": "^8.1.0", + "react-photo-view": "^1.2.7", "tailwind-merge": "^3.4.0", "tw-animate-css": "^1.4.0", "zustand": "^5.0.9", @@ -899,6 +900,8 @@ "react-map-gl": ["react-map-gl@8.1.0", "", { "dependencies": { "@vis.gl/react-mapbox": "8.1.0", "@vis.gl/react-maplibre": "8.1.0" }, "peerDependencies": { "mapbox-gl": ">=1.13.0", "maplibre-gl": ">=1.13.0", "react": ">=16.3.0", "react-dom": ">=16.3.0" }, "optionalPeers": ["mapbox-gl", "maplibre-gl"] }, "sha512-vDx/QXR3Tb+8/ap/z6gdMjJQ8ZEyaZf6+uMSPz7jhWF5VZeIsKsGfPvwHVPPwGF43Ryn+YR4bd09uEFNR5OPdg=="], + "react-photo-view": ["react-photo-view@1.2.7", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-MfOWVPxuibncRLaycZUNxqYU8D9IA+rbGDDaq6GM8RIoGJal592hEJoRAyRSI7ZxyyJNJTLMUWWL3UIXHJJOpw=="], + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], diff --git a/components/sidebar/intersection-sidebar.tsx b/components/sidebar/intersection-sidebar.tsx index a2fefd1..b69cdbd 100644 --- a/components/sidebar/intersection-sidebar.tsx +++ b/components/sidebar/intersection-sidebar.tsx @@ -1,5 +1,6 @@ "use client"; +import { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useMapStore } from "@/stores/map-store"; import { X, Map as MapIcon } from "lucide-react"; @@ -9,6 +10,14 @@ import { PhotoProvider } from "react-photo-view"; export function IntersectionSidebar() { const { selectedHotspot, selectHotspot } = useMapStore(); + const [activeTab, setActiveTab] = useState("overview"); + + // Reset to overview tab when hotspot changes + useEffect(() => { + if (selectedHotspot) { + setActiveTab("overview"); + } + }, [selectedHotspot?.id]); return ( {selectedHotspot ? ( @@ -55,34 +64,87 @@ export function IntersectionSidebar() { -
+
- + - Overview - Safety Audit - Re-imagine + + Overview + + + Safety Audit + + + Re-imagine + - - - +
+ + {activeTab === "overview" && ( + + + + )} - -
-

- Generate a safety audit to see AI analysis -

-
-
+ {activeTab === "audit" && ( + +

+ Generate a safety audit to see AI analysis +

+
+ )} - -
-

- Re-imagine this intersection with AI -

-
-
+ {activeTab === "reimagine" && ( + +

+ Re-imagine this intersection with AI +

+
+ )} +
+
diff --git a/components/sidebar/persona-sidebar.tsx b/components/sidebar/persona-sidebar.tsx index e710067..67ab1db 100644 --- a/components/sidebar/persona-sidebar.tsx +++ b/components/sidebar/persona-sidebar.tsx @@ -1,7 +1,7 @@ "use client"; -import { useState } from "react"; -import { motion } from "framer-motion"; +import { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; import { Volume2, Mic, Send, User, Building2, HardHat } from "lucide-react"; import { cn } from "@/lib/utils"; @@ -47,19 +47,38 @@ interface Message { content: string; } +const welcomeMessages: Record = { + cyclist: "Hi, I'm Marcus Chen. I was hit by a car at this intersection last year. What would you like to know about safety here?", + mp: "Hello, I'm Sarah Williams. As a Member of Parliament, I'm focused on improving infrastructure funding for safer intersections. How can I help?", + engineer: "Hi there, I'm James Okonkwo, a civil engineer specializing in traffic safety. What questions do you have about this intersection's design?", +}; + export function PersonaSidebar() { const [selectedPersona, setSelectedPersona] = useState(PERSONAS[0]); const [messages, setMessages] = useState([ { id: "1", role: "persona", - content: `Hi, I'm ${PERSONAS[0].name}. I was hit by a car at this intersection last year. What would you like to know about safety here?`, + content: welcomeMessages[PERSONAS[0].id], }, ]); const [input, setInput] = useState(""); const [isSpeaking, setIsSpeaking] = useState(false); const [isListening, setIsListening] = useState(false); + // Animate chat when switching personas + const handlePersonaChange = (persona: Persona) => { + setSelectedPersona(persona); + // Clear messages and show new welcome message with animation + setMessages([ + { + id: Date.now().toString(), + role: "persona", + content: welcomeMessages[persona.id], + }, + ]); + }; + const handleSend = () => { if (!input.trim()) return; @@ -109,7 +128,7 @@ export function PersonaSidebar() {

Voice Chat

@@ -120,9 +139,11 @@ export function PersonaSidebar() {

Select Persona

{PERSONAS.map((persona) => ( - + ))}
-
-
-
-
- {selectedPersona.icon} -
-
-
-

{selectedPersona.name}

-

{selectedPersona.role}

+
+ + +
+ +
+ {selectedPersona.icon} +
+
+ +

{selectedPersona.name}

+

{selectedPersona.role}

+
-
-

{selectedPersona.description}

+ + {selectedPersona.description} + + +
-
- {messages.map((message) => ( -
-
+ + {messages.map((message, index) => ( + -

{message.content}

-
- {message.role === "persona" && ( - - )} -
- ))} +

{message.content}

+ + {message.role === "persona" && ( + + + + )} + + ))} +
diff --git a/next.config.ts b/next.config.ts index fa67b35..cb651cd 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,9 +1,5 @@ import type { NextConfig } from "next"; -const nextConfig: NextConfig = { - turbopack: { - root: __dirname, - }, -}; +const nextConfig: NextConfig = {}; export default nextConfig; From e660823b5babe4d9225183c836c3bb902d858652 Mon Sep 17 00:00:00 2001 From: DhanNarula Date: Sat, 10 Jan 2026 18:56:44 -0500 Subject: [PATCH 2/2] added animations to the menu navigations and removed the white scroll barss --- components/sidebar/intersection-sidebar.tsx | 73 +++++++++++++++------ components/sidebar/persona-sidebar.tsx | 4 +- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/components/sidebar/intersection-sidebar.tsx b/components/sidebar/intersection-sidebar.tsx index e428272..a5e8bee 100644 --- a/components/sidebar/intersection-sidebar.tsx +++ b/components/sidebar/intersection-sidebar.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useMapStore } from "@/stores/map-store"; import { X } from "lucide-react"; @@ -11,14 +11,43 @@ import { PhotoProvider } from "react-photo-view"; export function IntersectionSidebar() { const { selectedHotspot, selectHotspot } = useMapStore(); const [activeTab, setActiveTab] = useState("overview"); + const [direction, setDirection] = useState(1); + + // Tab order for directional animations + const tabOrder = ["overview", "audit", "reimagine"]; // Reset to overview tab when hotspot changes useEffect(() => { if (selectedHotspot) { setActiveTab("overview"); + setDirection(1); } }, [selectedHotspot?.id]); + // Handle tab change + const handleTabChange = (newTab: string) => { + const currentIndex = tabOrder.indexOf(activeTab); + const newIndex = tabOrder.indexOf(newTab); + const newDirection = newIndex > currentIndex ? 1 : -1; + setDirection(newDirection); + setActiveTab(newTab); + }; + + const variants = { + enter: (direction: number) => ({ + x: direction > 0 ? 80 : -80, + opacity: 0, + }), + center: { + x: 0, + opacity: 1, + }, + exit: (direction: number) => ({ + x: direction < 0 ? 80 : -80, + opacity: 0, + }), + }; + return ( {selectedHotspot && ( @@ -69,9 +98,9 @@ export function IntersectionSidebar() {
-
+
- +
- + {activeTab === "overview" && ( @@ -114,12 +145,14 @@ export function IntersectionSidebar() { {activeTab === "audit" && ( {/* Agent List */} -
+
{PERSONAS.map((persona, index) => ( {/* Transcript Area */} -
+
{transcriptMessages.length === 0 && !isCallInProgress && (