fix: filter panel overlap, Wallet & Stash rename, Sasquatch on all pages

Filter panel (MapPage):
- Sheet now h-[82vh] max-h-[680px] with flex-col layout
- Scrollable content area uses flex-1 min-h-0 (proper flex children)
- "Show Results" button is now flex-shrink-0 at bottom of column
- No more overlap between Show Results and Stash/Tour quick action buttons

Wallet & Stash rename:
- Settings tab renamed from "Wallet" to "Wallet & Stash"
- "Me" dropdown in mobile header shows "Wallet & Stash" for the wallet link

Sasquatch persistent button (all pages):
- Minimized bubble is now ALWAYS visible on every page (not just pages with a guide message)
- Tapping the bubble on any page opens Sasquatch with a generic helpful greeting
- Pages with specific context (/, /map, /lab, /onboarding) still show their custom message
- Pages without context (/wallet, /settings, /stash, /events, etc.) now show a generic "Ask me anything" greeting with common questions
- Removed the separate X close button (minimize was already the same) — bubble stays visible as entry point at all times
- onPointerUp now also sets isVisible=true so panel opens on pages without a message

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-07-23 16:45:11 +07:00
parent 340f6fd89f
commit 0dc0d272e1
4 changed files with 49 additions and 35 deletions

View file

@ -204,7 +204,7 @@ function MobileHeaderUserMenu({
<DropdownMenuItem asChild>
<Link to="/wallet" className="flex items-center gap-2 cursor-pointer">
<Wallet className="h-4 w-4 text-orange-400" />
<span className="text-orange-400">Wallet</span>
<span className="text-orange-400">Wallet &amp; Stash</span>
</Link>
</DropdownMenuItem>
<DropdownMenuSeparator />

View file

@ -358,7 +358,9 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
dragMoved.current = false;
setTimeout(() => setIsDragging(false), 50);
if (!wasDrag) {
// Always open the panel — even on pages without a context message
setIsMinimized(false);
setIsVisible(true);
}
}, [setIsMinimized]);
@ -576,16 +578,33 @@ Instructions:
};
}, [handleSendMessage, setIsMinimized]);
// Show if we have a message prop OR if we've been opened externally
const shouldShow = !!message || isVisible;
if (!shouldShow) return null;
// The minimized bubble is ALWAYS visible so the user can reopen Sasquatch on any page.
// The full panel shows only when there's a message or when opened externally/by user tap.
const shouldShowPanel = !!message || isVisible;
const inputDisabled = isThinking;
// Build a fallback generic message for pages with no specific context
const activeMessage = message ?? {
id: 'generic',
title: 'SkateSpots Assistant 🦧',
message: "Yo! I'm The Sasquatch. Ask me anything about spots, events, coaches, gear, or how the app works. I've got live data from the SkateSpots relay!",
context: undefined,
suggestedQuestions: [
'Find spots near me',
'Any upcoming events?',
'What is Nostr?',
'How do zaps work?',
'What is My Stash?',
'How to add a spot?',
],
enableChat: true,
};
return (
<>
{/* ── Minimized bubble — draggable ── */}
{isMinimized && (
{/* ── Minimized bubble — always visible, draggable ── */}
{(!shouldShowPanel || isMinimized) && (
<div
ref={dragRef}
className={cn(
@ -621,7 +640,7 @@ Instructions:
)}
{/* ── Full panel ── */}
{!isMinimized && (
{shouldShowPanel && !isMinimized && (
<div
className={cn(
'fixed z-50 transition-all duration-300 ease-out',
@ -665,24 +684,19 @@ Instructions:
<CVMStatus connected={cvmConnected} />
</div>
<div className="text-[10px] text-[#ccff00]/50 leading-none mt-0.5">
{message.title}
{activeMessage.title}
</div>
</div>
<div className="flex gap-1 flex-shrink-0">
{/* Minimize — keeps the bubble visible */}
<button
onClick={() => setIsMinimized(true)}
className="w-6 h-6 flex items-center justify-center rounded text-[#ccff00]/40 hover:text-[#ccff00] hover:bg-[#ccff00]/10 transition-colors"
aria-label="Minimize"
aria-label="Minimize to bubble"
title="Minimize — bubble stays visible so you can reopen anytime"
>
<Minimize2 className="h-3 w-3" />
</button>
<button
onClick={() => setIsMinimized(true)}
className="w-6 h-6 flex items-center justify-center rounded text-[#ccff00]/40 hover:text-[#ff0040] hover:bg-[#ff0040]/10 transition-colors"
aria-label="Close"
>
<X className="h-3 w-3" />
</button>
</div>
</div>
@ -690,21 +704,21 @@ Instructions:
<div className="p-3">
{/* Greeting */}
<p className="text-xs text-gray-300 leading-relaxed mb-3">
{message.message}
{activeMessage.message}
</p>
{/* Action button */}
{message.action && (
{activeMessage.action && (
<button
onClick={message.action.onClick}
onClick={activeMessage.action.onClick}
className="w-full py-2 px-3 rounded-lg text-xs font-bold text-black bg-[#ccff00] hover:bg-[#d4ff1a] transition-colors mb-3"
>
{message.action.label}
{activeMessage.action.label}
</button>
)}
{/* Chat section */}
{message.enableChat && (
{activeMessage.enableChat && (
<div className="border-t border-[#ccff00]/10 pt-3">
{!showChat ? (
<button
@ -712,21 +726,21 @@ Instructions:
className="w-full py-2 px-3 rounded-lg text-xs font-bold text-[#ccff00] border border-[#ccff00]/30 hover:border-[#ccff00]/60 hover:bg-[#ccff00]/5 transition-all flex items-center justify-center gap-1.5"
>
<HelpCircle className="h-3 w-3" />
{message.context === 'lab' && 'Ask about Coaching & Filming'}
{message.context === 'map' && 'Ask about Spots, Shops & Gear'}
{(message.context === 'home' || message.context === 'onboarding') && 'Ask me anything 🛹'}
{!message.context && 'Ask me anything 🛹'}
{activeMessage.context === 'lab' && 'Ask about Coaching & Filming'}
{activeMessage.context === 'map' && 'Ask about Spots, Shops & Gear'}
{(activeMessage.context === 'home' || activeMessage.context === 'onboarding') && 'Ask me anything 🛹'}
{(!activeMessage.context || (activeMessage.context !== 'lab' && activeMessage.context !== 'map' && activeMessage.context !== 'home' && activeMessage.context !== 'onboarding')) && 'Ask me anything 🛹'}
</button>
) : (
<div className="space-y-2.5">
{/* Suggested questions */}
{chatMessages.length === 0 && message.suggestedQuestions && (
{chatMessages.length === 0 && activeMessage.suggestedQuestions && (
<div className="space-y-1">
<div className="text-[9px] text-[#ccff00]/50 font-bold uppercase tracking-wider mb-1.5">
Quick questions:
</div>
{message.suggestedQuestions.map((q) => (
{activeMessage.suggestedQuestions.map((q) => (
<button
key={q}
onClick={() => handleSendMessage(q)}

View file

@ -682,8 +682,8 @@ export default function MapPage() {
)}
</Button>
</SheetTrigger>
<SheetContent side="bottom" className="h-[70vh] max-h-[600px] rounded-t-2xl px-4">
<SheetHeader className="pb-2">
<SheetContent side="bottom" className="h-[82vh] max-h-[680px] rounded-t-2xl px-4 flex flex-col">
<SheetHeader className="pb-2 flex-shrink-0">
<SheetTitle className="flex items-center justify-between text-base">
<span>Filters</span>
{hasActiveFilters && (
@ -702,7 +702,7 @@ export default function MapPage() {
</SheetTitle>
</SheetHeader>
<div className="space-y-4 overflow-y-auto pb-20 -mx-4 px-4" style={{ maxHeight: 'calc(70vh - 120px)' }}>
<div className="space-y-4 overflow-y-auto flex-1 min-h-0 -mx-4 px-4 pb-2 scroll-container">
{/* Layer toggles */}
<div>
<h3 className="text-xs font-bold text-muted-foreground mb-2 uppercase tracking-wide">Show on Map</h3>
@ -858,10 +858,10 @@ export default function MapPage() {
)}
</div>
{/* Apply button fixed at bottom */}
<div className="absolute bottom-0 left-0 right-0 p-3 bg-background border-t safe-area-bottom">
{/* Show Results button — sticks to bottom, never overlaps content */}
<div className="flex-shrink-0 pt-3 pb-2 border-t safe-area-bottom bg-background">
<Button
className="w-full h-11 font-bold"
className="w-full h-12 font-bold text-base"
onClick={() => setMobileFiltersOpen(false)}
>
Show Results

View file

@ -659,9 +659,9 @@ export default function SettingsPage() {
<Radio className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] font-semibold whitespace-nowrap">Relays</span>
</TabsTrigger>
<TabsTrigger value="wallet" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<TabsTrigger value="wallet" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[72px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Wallet className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] font-semibold whitespace-nowrap">Wallet</span>
<span className="text-[10px] font-semibold whitespace-nowrap">Wallet & Stash</span>
</TabsTrigger>
<TabsTrigger value="advanced" className="flex-col gap-1 px-3 py-2.5 h-auto min-w-[64px] sm:min-w-0 rounded-lg data-[state=active]:bg-background">
<Wrench className="h-4 w-4 flex-shrink-0" />