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> <DropdownMenuItem asChild>
<Link to="/wallet" className="flex items-center gap-2 cursor-pointer"> <Link to="/wallet" className="flex items-center gap-2 cursor-pointer">
<Wallet className="h-4 w-4 text-orange-400" /> <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> </Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator />

View file

@ -358,7 +358,9 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
dragMoved.current = false; dragMoved.current = false;
setTimeout(() => setIsDragging(false), 50); setTimeout(() => setIsDragging(false), 50);
if (!wasDrag) { if (!wasDrag) {
// Always open the panel — even on pages without a context message
setIsMinimized(false); setIsMinimized(false);
setIsVisible(true);
} }
}, [setIsMinimized]); }, [setIsMinimized]);
@ -576,16 +578,33 @@ Instructions:
}; };
}, [handleSendMessage, setIsMinimized]); }, [handleSendMessage, setIsMinimized]);
// Show if we have a message prop OR if we've been opened externally // The minimized bubble is ALWAYS visible so the user can reopen Sasquatch on any page.
const shouldShow = !!message || isVisible; // The full panel shows only when there's a message or when opened externally/by user tap.
if (!shouldShow) return null; const shouldShowPanel = !!message || isVisible;
const inputDisabled = isThinking; 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 ( return (
<> <>
{/* ── Minimized bubble — draggable ── */} {/* ── Minimized bubble — always visible, draggable ── */}
{isMinimized && ( {(!shouldShowPanel || isMinimized) && (
<div <div
ref={dragRef} ref={dragRef}
className={cn( className={cn(
@ -621,7 +640,7 @@ Instructions:
)} )}
{/* ── Full panel ── */} {/* ── Full panel ── */}
{!isMinimized && ( {shouldShowPanel && !isMinimized && (
<div <div
className={cn( className={cn(
'fixed z-50 transition-all duration-300 ease-out', 'fixed z-50 transition-all duration-300 ease-out',
@ -665,24 +684,19 @@ Instructions:
<CVMStatus connected={cvmConnected} /> <CVMStatus connected={cvmConnected} />
</div> </div>
<div className="text-[10px] text-[#ccff00]/50 leading-none mt-0.5"> <div className="text-[10px] text-[#ccff00]/50 leading-none mt-0.5">
{message.title} {activeMessage.title}
</div> </div>
</div> </div>
<div className="flex gap-1 flex-shrink-0"> <div className="flex gap-1 flex-shrink-0">
{/* Minimize — keeps the bubble visible */}
<button <button
onClick={() => setIsMinimized(true)} 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" 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" /> <Minimize2 className="h-3 w-3" />
</button> </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>
</div> </div>
@ -690,21 +704,21 @@ Instructions:
<div className="p-3"> <div className="p-3">
{/* Greeting */} {/* Greeting */}
<p className="text-xs text-gray-300 leading-relaxed mb-3"> <p className="text-xs text-gray-300 leading-relaxed mb-3">
{message.message} {activeMessage.message}
</p> </p>
{/* Action button */} {/* Action button */}
{message.action && ( {activeMessage.action && (
<button <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" 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> </button>
)} )}
{/* Chat section */} {/* Chat section */}
{message.enableChat && ( {activeMessage.enableChat && (
<div className="border-t border-[#ccff00]/10 pt-3"> <div className="border-t border-[#ccff00]/10 pt-3">
{!showChat ? ( {!showChat ? (
<button <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" 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" /> <HelpCircle className="h-3 w-3" />
{message.context === 'lab' && 'Ask about Coaching & Filming'} {activeMessage.context === 'lab' && 'Ask about Coaching & Filming'}
{message.context === 'map' && 'Ask about Spots, Shops & Gear'} {activeMessage.context === 'map' && 'Ask about Spots, Shops & Gear'}
{(message.context === 'home' || message.context === 'onboarding') && 'Ask me anything 🛹'} {(activeMessage.context === 'home' || activeMessage.context === 'onboarding') && 'Ask me anything 🛹'}
{!message.context && 'Ask me anything 🛹'} {(!activeMessage.context || (activeMessage.context !== 'lab' && activeMessage.context !== 'map' && activeMessage.context !== 'home' && activeMessage.context !== 'onboarding')) && 'Ask me anything 🛹'}
</button> </button>
) : ( ) : (
<div className="space-y-2.5"> <div className="space-y-2.5">
{/* Suggested questions */} {/* Suggested questions */}
{chatMessages.length === 0 && message.suggestedQuestions && ( {chatMessages.length === 0 && activeMessage.suggestedQuestions && (
<div className="space-y-1"> <div className="space-y-1">
<div className="text-[9px] text-[#ccff00]/50 font-bold uppercase tracking-wider mb-1.5"> <div className="text-[9px] text-[#ccff00]/50 font-bold uppercase tracking-wider mb-1.5">
Quick questions: Quick questions:
</div> </div>
{message.suggestedQuestions.map((q) => ( {activeMessage.suggestedQuestions.map((q) => (
<button <button
key={q} key={q}
onClick={() => handleSendMessage(q)} onClick={() => handleSendMessage(q)}

View file

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

View file

@ -659,9 +659,9 @@ export default function SettingsPage() {
<Radio className="h-4 w-4 flex-shrink-0" /> <Radio className="h-4 w-4 flex-shrink-0" />
<span className="text-[10px] font-semibold whitespace-nowrap">Relays</span> <span className="text-[10px] font-semibold whitespace-nowrap">Relays</span>
</TabsTrigger> </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" /> <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>
<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"> <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" /> <Wrench className="h-4 w-4 flex-shrink-0" />