451 lines
38 KiB
TypeScript
451 lines
38 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Input } from '@/components/ui/input';
|
||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||
import { X, Minimize2, HelpCircle, Send } from 'lucide-react';
|
||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||
import { cn } from '@/lib/utils';
|
||
|
||
export type SasquatchContext = 'home' | 'onboarding' | 'map' | 'lab';
|
||
|
||
export interface GuideMessage {
|
||
id: string;
|
||
title: string;
|
||
message: string;
|
||
context?: SasquatchContext; // Context for FAQ responses
|
||
suggestedQuestions?: string[]; // Quick questions users can click
|
||
action?: {
|
||
label: string;
|
||
onClick: () => void;
|
||
};
|
||
enableChat?: boolean; // Allow Q&A on this message
|
||
}
|
||
|
||
interface SasquatchGuideProps {
|
||
message?: GuideMessage;
|
||
onDismiss?: () => void;
|
||
}
|
||
|
||
interface ChatMessage {
|
||
role: 'user' | 'sasquatch';
|
||
text: string;
|
||
timestamp: number;
|
||
}
|
||
|
||
// Context-specific FAQ responses
|
||
type FAQResponses = Record<string, string>;
|
||
|
||
// Home & Onboarding - Getting started, Nostr basics, Bitcoin basics
|
||
const HOME_ONBOARDING_FAQ: FAQResponses = {
|
||
// Nostr basics - Full explanations
|
||
'what is nostr': "Nostr stands for 'Notes and Other Stuff Transmitted by Relays'. It's a simple, open protocol for decentralized social networks. Think Twitter, but no one can ban you, censor you, or control your data. You use cryptographic keys (like a password that can't be reset) instead of usernames. Your identity is portable across all Nostr apps - one account works everywhere! No company owns Nostr, it's just a protocol like email.",
|
||
|
||
'how does nostr work': "Here's the technical overview: You get a public key (npub - your identity, shareable) and a private key (nsec - for signing, keep secret!). When you post, your content is cryptographically signed with your private key and sent to relays (simple servers). Apps like SkateSpots connect to these relays to fetch and display content. No blockchain, no mining, no tokens - just a simple, elegant protocol that puts YOU in control of your data and identity!",
|
||
|
||
'what is relay': "Relays are lightweight servers that store and forward your Nostr events (posts, profiles, etc.). Think of them like post offices - you can send mail through any post office, and it still reaches its destination. You can use any relay, switch anytime, run your own, or use multiple simultaneously. This app connects to several relays for redundancy, so your content is always available even if one relay goes down. Relays are simple, cheap to run, and keep Nostr censorship-resistant!",
|
||
|
||
// Bitcoin & Lightning - Basics for skaters
|
||
'what is bitcoin': "Bitcoin is digital money that no government or company controls. Created in 2009, it's peer-to-peer electronic cash - you can send it directly to anyone, anywhere, instantly. No banks needed! For skateboarding, it's perfect: permissionless (no one can stop you), censorship-resistant (can't be frozen), and global (works everywhere). Bitcoin is programmed to be scarce - only 21 million will ever exist - making it valuable like digital gold. Perfect for supporting skaters and content creators worldwide! 🪙",
|
||
|
||
'what are zaps': "Zaps are instant Bitcoin Lightning tips sent over Nostr! When you see a sick spot, killer trick video, or helpful content, you can zap the creator - sending them Bitcoin in milliseconds. It goes directly to their wallet, no middlemen taking cuts. Zaps are tied to your Nostr identity, so creators can see who appreciated their work. It's like hitting 'like' but with real monetary value! ⚡ Think of it as buying someone a coffee, but digital and instant.",
|
||
|
||
// Getting started - Step-by-step
|
||
'how to login': "Step-by-step login guide:\n\n1. **Browser Extension (Desktop)**: Install Alby, nos2x, or Flamingo extension. Click 'Log in' in top-right, then approve the connection request.\n\n2. **Mobile/New User**: Click 'Sign up' to generate new keys. Your browser will download your nsec (secret key) - SAVE IT SAFELY! This is your account - lose it and you lose everything.\n\n3. **Existing User**: Click 'Log in', then 'Paste nsec' if you already have Nostr keys.\n\nNew to Nostr? Choose 'Sign up' - it takes 30 seconds! 🎯",
|
||
|
||
// Privacy & Security
|
||
'is it safe': "YES! Privacy and security breakdown:\n\n✅ **Cryptography**: Nostr uses military-grade elliptic curve cryptography (same as Bitcoin). Your private key proves you own your account.\n\n✅ **Data Control**: You own your data. Posts are signed by YOUR key and stored on relays you choose.\n\n✅ **Privacy**: No email required, no phone number, no personal info needed to sign up.\n\n⚠️ **Important**: NEVER share your nsec (private key) with anyone! Use browser extensions like Alby to keep it secure. If someone gets your nsec, they control your account forever.\n\nFor more info: https://nostr.how/en/guides/sweep-your-nsec",
|
||
|
||
// Lightning & other
|
||
'what is lightning': "Lightning Network is Bitcoin's instant payment layer - like Bitcoin on steroids! Send or receive Bitcoin in milliseconds for tiny fees (fractions of a cent). Perfect for tips, small transactions, and everyday use. It works by creating payment channels between users, allowing thousands of transactions per second. For SkateSpots, Lightning enables instant zaps to reward creators! ⚡",
|
||
|
||
'what is nwc': "NWC (Nostr Wallet Connect) is like USB-C for Bitcoin wallets! It's an open protocol that lets you connect your Lightning wallet to apps like SkateSpots. Instead of copying invoices or switching between apps, NWC lets you zap directly with one click. You connect once (paste a connection string from your wallet), and boom - instant zaps forever! Works with Alby, Minibits, Zeus, Coinos, and other modern wallets. It's secure (encrypted over Nostr), you control spending limits, and you can revoke access anytime. Think of it as signing into SkateSpots with your wallet! 🔌⚡",
|
||
|
||
'nostr wallet connect': "NWC (Nostr Wallet Connect) is like USB-C for Bitcoin wallets! It's an open protocol that lets you connect your Lightning wallet to apps like SkateSpots. Instead of copying invoices or switching between apps, NWC lets you zap directly with one click. You connect once (paste a connection string from your wallet), and boom - instant zaps forever! Works with Alby, Minibits, Zeus, Coinos, and other modern wallets. It's secure (encrypted over Nostr), you control spending limits, and you can revoke access anytime. Think of it as signing into SkateSpots with your wallet! 🔌⚡",
|
||
|
||
'how to zap': "First, connect a Lightning wallet (like Alby extension, or Minibits/Zeus/Coinos with NWC). Then click the ⚡ button on any content. Enter an amount in sats (1 sat ≈ $0.0004) and confirm. The sats go directly to the creator instantly! Common amounts: 21 sats (small tip), 210 sats (nice!), 2100 sats (generous!), 21000 sats (wow!).",
|
||
|
||
'do i need bitcoin': "Nope! You can browse spots, view events, explore the map, and connect with the community - all 100% FREE without any Bitcoin. But if you want to zap creators or earn sats for contributing spots/events, you'll need a Lightning wallet. Think of Bitcoin as optional premium features, not a requirement!",
|
||
|
||
'is this free': "100% free! No subscriptions, no ads, no corporate BS. Built by skaters, for skaters. You can earn Bitcoin by contributing quality spots and events (through community zaps), but the core app is completely free to use forever!",
|
||
|
||
'what can i do': "Discover spots worldwide, connect with pros at The Lab (coaches & filmers), join events and sessions, connect with skaters, earn sats for quality contributions, zap creators you appreciate, check weather conditions before skating, and help build the decentralized skate community! Everything is free except optional zaps.",
|
||
|
||
'who owns my data': "You do! Your posts are cryptographically signed with your keys and stored on relays. You can backup your keys, switch apps anytime, and no one can delete your content without your permission. Not even SkateSpots can censor you - that's the power of Nostr!",
|
||
|
||
// Trust Scores & Relays
|
||
'what are trust scores': "Trust scores help identify reliable skaters and reduce spam! SkateSpots uses Relatr - a service that analyzes the entire Nostr network to compute global trust scores (0-100). It looks at factors like followers, activity, engagement, and zaps sent/received. Higher scores = more trusted accounts. This is auto-enabled for all users and helps protect the community from bots and spam. You can see trust scores on profiles and use them to verify coaches, shop owners, and other skaters. It's like reputation points for Nostr! 🛡️",
|
||
|
||
'trust score': "Trust scores help identify reliable skaters and reduce spam! SkateSpots uses Relatr - a service that analyzes the entire Nostr network to compute global trust scores (0-100). It looks at factors like followers, activity, engagement, and zaps sent/received. Higher scores = more trusted accounts. This is auto-enabled for all users and helps protect the community from bots and spam. You can see trust scores on profiles and use them to verify coaches, shop owners, and other skaters. It's like reputation points for Nostr! 🛡️",
|
||
|
||
'what is relatr': "Relatr is a trust scoring service powered by ContextVM that analyzes the entire Nostr network. It publishes global trust scores (NIP-85 events) to relays, helping apps like SkateSpots identify trustworthy users and filter spam. Think of it as a decentralized reputation system - instead of trusting one company's algorithm, Relatr computes scores from real network data (followers, engagement, zaps, activity). It's enabled by default in SkateSpots to protect the community! You can customize it in Settings → Advanced if you want. 🔍",
|
||
|
||
'what is nip85': "NIP-85 is a Nostr protocol for 'Trusted Assertions' - a way for services to publish trust and reputation data about users. Relatr uses NIP-85 to publish trust scores (ranks 0-100) along with metadata like follower counts, post counts, and zap amounts. Other apps can read these scores from relays to help filter content and identify trustworthy accounts. It's like a decentralized credit score, but for social networks! SkateSpots reads NIP-85 events from Relatr to show trust scores on profiles. 📊",
|
||
|
||
'how do relays work': "Relays store and forward your Nostr events! When you post, your content is sent to all your configured relays. When you browse, SkateSpots queries relays to fetch content. SkateSpots uses 2 default relays:\n\n1. 🛹 **goskatespots.nostr1.com** - Our primary skate relay (allowlisted for quality)\n2. 🛡️ **wotr.relatr.xyz** - Relatr's Web of Trust relay for trust scores + redundancy\n\nYou can manage relays in Settings → Relays. Want to run your own relay? Check out relay.tools! ⚡",
|
||
|
||
'what relays does this use': "SkateSpots uses 2 default relays:\n\n1. 🛹 **goskatespots.nostr1.com** - Our primary skate relay (allowlisted for quality)\n2. 🛡️ **wotr.relatr.xyz** - Relatr's Web of Trust relay for trust scores + redundancy\n\nYour posts go to both, so even if one is down, your content stays available! The primary relay uses an allowlist to prevent spam - DM the relay owner for access. The backup ensures your content always gets published. Want to run your own relay? Check out relay.tools! 📡",
|
||
|
||
'what is goskatespots relay': "goskatespots.nostr1.com is our primary skateboarding relay! It has custom policies for spots, events, coaches, filmers, and shops. It uses an **allowlist** for trust and spam management - your npub must be specifically approved to publish here. This keeps the quality high and spam low! If you're not on the allowlist yet, no worries - your content still publishes to the backup relay (wotr.relatr.xyz) so it's never lost. DM the relay owner to request access! 🛹",
|
||
|
||
'why cant i publish': "If you're getting publish errors, it's usually because you're not on the goskatespots.nostr1.com allowlist yet. Good news: your content STILL gets published to the backup relay (wotr.relatr.xyz), so nothing is lost! Everyone can see your spots, events, and posts. To get allowlisted on the primary relay:\n\n1. Build trust - contribute quality spots, be active in the community\n2. DM the relay owner with your npub\n3. Wait for approval\n\nIn the meantime, keep posting - the backup has you covered! 📝",
|
||
|
||
'allowlist relay': "The primary relay (goskatespots.nostr1.com) uses an allowlist to maintain quality and prevent spam. Only approved npubs can publish there. But don't worry - you have a backup relay (wotr.relatr.xyz) that accepts posts from everyone! Your content still publishes and everyone can see it. Think of the allowlist like a VIP club - it's exclusive, but you don't NEED it to participate. Request access by DMing the relay owner, or just keep using the backup! 🎫",
|
||
|
||
'how to manage relays': "Go to Settings → Relays! You'll see your 2 default relays and can add/remove relays as needed. Each relay has read/write toggles:\n\n- **Write (publish)**: Your posts go to these relays\n- **Read (fetch)**: You fetch content from these relays\n\nFor best results, enable both read+write on all relays. Want to run your own relay? Check out relay.tools to easily spin up and manage your own Nostr relay! Pro tip: If you're having issues, try removing and re-adding problematic relays. ⚙️",
|
||
|
||
// Cashu Wallet
|
||
'what is cashu': "Cashu is digital cash backed by Bitcoin Lightning! It's like holding Bitcoin tokens directly in your browser - no external wallet needed. You can send and receive instantly, it works offline at skate spots, and it's privacy-focused (mints don't track who you pay). Think of it as cash in your pocket, but digital! Cashu tokens are always redeemable for Lightning sats, so you're never stuck. Your wallet is built right into SkateSpots - go to Settings → Wallet to use it! Learn more: https://cashumints.space 🥜",
|
||
|
||
'how does cashu work': "Cashu uses ecash technology - digital tokens backed by Bitcoin held at mints (servers). When you receive Lightning, a mint converts it to tokens stored in your browser. When you send Lightning, tokens are converted back. The cool part: mints can't see who pays who (privacy!), and you can use multiple mints for redundancy (like multiple relays!). Your tokens are secured by a 12-word backup phrase. It's like having a Bitcoin wallet built into SkateSpots - works offline, instant, and private! 💰",
|
||
|
||
'cashu wallet backup': "Your Cashu wallet has a 12-word recovery phrase (seed) - just like your Nostr nsec! Go to Settings → Wallet → Cashu Wallet → View Recovery Phrase to see your words. Write them down on paper and store safely in a secure place. If you clear browser data or switch devices, you'll need these words to restore your balance. ⚠️ NEVER share your seed with anyone - it's like a password that can't be reset! Anyone with your seed controls your wallet. Keep it as secure as your Nostr keys! 🔐",
|
||
|
||
'what are mints': "Mints are servers that issue Cashu tokens backed by Bitcoin Lightning. Think of them like banks, but open and interoperable - any wallet can use any mint! When you receive Lightning, a mint converts it to tokens. When you send Lightning, a mint converts tokens back. SkateSpots uses mint.minibits.cash by default, but you can add more in Settings → Wallet → Manage Mints. Using multiple mints is RECOMMENDED for redundancy (like multiple Nostr relays!). If one mint goes down, others keep working. Check https://cashumints.space to explore all available mints. 🏦",
|
||
|
||
'how to use cashu wallet': "Go to Settings → Wallet and scroll to the Cashu Wallet section!\n\n**To receive:**\n1. Enter amount in sats\n2. Click 'Generate Invoice'\n3. Pay from any Lightning wallet\n4. Tokens appear in your balance!\n\n**To send:**\n1. Enter amount\n2. Click 'Create Token'\n3. Share the QR code or copy the token\n4. Recipient redeems it!\n\nYou can also pay Lightning invoices directly or receive Cashu tokens from other users. It's all built-in - no external app needed! 🛹⚡",
|
||
|
||
'cashu vs lightning': "Both use Bitcoin Lightning, but differently:\n\n**Lightning Wallet (NWC):**\n• External app connected to SkateSpots\n• Requires setup\n• Always online\n• More established\n\n**Cashu Wallet (Built-in):**\n• Built into SkateSpots\n• No setup needed\n• Works offline\n• Privacy-focused\n\n**Best practice:** Use BOTH for redundancy! Cashu for quick payments at skate spots, Lightning/NWC for larger amounts and long-term storage. They work together - you can move sats between them anytime! 💪",
|
||
|
||
'is cashu safe': "Yes! Cashu uses cryptography (same tech as Bitcoin) to secure your tokens. Your balance is stored locally in your browser, and you control the seed. Important notes:\n\n✅ Mints are trusted servers (like relays) that back your tokens with Bitcoin\n✅ Use multiple mints for redundancy\n✅ Always backup your seed phrase\n✅ Tokens are always redeemable for Lightning sats\n⚠️ If a mint shuts down, withdraw your sats to Lightning first\n\nThink of mints like banks - use reputable ones and spread your balance! Check https://cashumints.space for trusted mints. 💰",
|
||
};
|
||
|
||
// The Lab - Coaching, filming, and services
|
||
const LAB_FAQ: FAQResponses = {
|
||
'what is the lab': "The Lab is where skaters level up! It's our marketplace for coaching and filming services. Find experienced skaters who can teach you tricks, or connect with filmers and photographers to capture your sessions. All profiles are on Nostr, so you know they're real skaters, not fake accounts. You can filter by skills (street, vert, transitions, etc.) and location. Want to earn sats teaching? You can offer your own services too!",
|
||
|
||
'what is lab': "The Lab is where skaters level up! It's our marketplace for coaching and filming services. Find experienced skaters who can teach you tricks, or connect with filmers and photographers to capture your sessions. All profiles are on Nostr, so you know they're real skaters, not fake accounts. You can filter by skills (street, vert, transitions, etc.) and location. Want to earn sats teaching? You can offer your own services too!",
|
||
|
||
'how to find coach': "Use the filters at the top! Select the 'Coaching' tab, then pick skills you want to learn (kickflips, grinds, bowl riding, etc.). Browse coaches in your area - each profile shows their experience, specialties, rates, and bio. Click any coach to see their full profile and contact them directly through Nostr. Pro tip: Look for coaches with NIP-05 verification (the green checkmark) - they're verified skaters!",
|
||
|
||
'how to find filmer': "Switch to the 'Filming' tab at the top! You'll find photographers and videographers who specialize in skate content. Filter by service type (photos, video editing, full sessions, etc.) and search by location. Check their portfolios, rates, and experience, then reach out directly. Many filmers accept Bitcoin payments too! 📹",
|
||
|
||
'how to offer coaching': "Click 'Offer Coaching' at the top! Fill out your profile: years of experience, what you can teach (street, vert, technical tricks, etc.), your location, and rates. Add photos and a detailed bio so skaters know who they're learning from. Your profile lives on Nostr, so you own it forever. Start earning sats while helping skaters progress!",
|
||
|
||
'how to become coach': "Click 'Offer Coaching' at the top! Fill out your profile: years of experience, what you can teach (street, vert, technical tricks, etc.), your location, and rates. Add photos and a detailed bio so skaters know who they're learning from. Your profile lives on Nostr, so you own it forever. Start earning sats while helping skaters progress!",
|
||
|
||
'how to offer filming': "Click 'Offer Filming' at the top! Detail what services you provide (filming sessions, photo shoots, video editing, slow-mo, etc.), your gear setup, experience, and pricing. Upload sample work to show your style. Once published, skaters can find you and book sessions. All payment terms are between you and the client - total freedom!",
|
||
|
||
'what are skills': "Skills are what coaches can teach! Options include: 🛹 Street (ledges, rails, stairs, gaps), 🏗️ Vert (half pipes, ramps), 🌊 Transitions (quarter pipes, mini ramps), 🎪 Bowls (pool riding), 💪 Technical (flip tricks, manuals), and more. Filter by what you want to learn, and we'll match you with coaches who specialize in it!",
|
||
|
||
'how much does coaching cost': "Rates vary by coach! Each profile shows their pricing - some charge hourly, others offer package deals. You'll see rates like '$50/hour' or 'Negotiable'. Many coaches also accept Bitcoin Lightning payments. Contact coaches directly to discuss rates and book sessions. Pro tip: Some coaches offer discounts for multiple sessions!",
|
||
|
||
'can i message coaches': "Yes! Click any coach profile and you'll see their contact info. Most coaches list their Nostr pubkey, so you can message them directly through any Nostr client. Some also include email, phone, or social media. All communication happens off-platform - we just connect you!",
|
||
|
||
'nip05 verification': "The green checkmark means they have NIP-05 verification! This proves they control a domain (like theirname@skateshop.com). It's not required, but it adds credibility - you know they're a real person/business. Verified coaches and filmers are more trustworthy!",
|
||
};
|
||
|
||
// Map/Spots/Shops - Navigation, adding spots, finding shops, gear swap
|
||
const MAP_FAQ: FAQResponses = {
|
||
'how to use map': "Easy! The map shows three types of markers: 🛹 Skate Spots (parks, street spots), 🏪 Skate Shops (where to buy gear), and 🛒 Gear Swap (peer-to-peer marketplace at spots). Toggle them on/off with the buttons above the map. Click any marker to see details, or switch to list view to filter by spot type (street, park, bowl, etc.). Click anywhere on the map to add a new spot!",
|
||
|
||
'how to add a spot': "Click anywhere on the map to drop a pin at that location! Add photos of the spot, describe the features (rails, ledges, stairs, quarter pipes, etc.), tag the spot type (street, park, diy, etc.), and add access info. Hit publish and boom - it's on Nostr forever! Your contribution helps skaters worldwide find new places to shred. 🛹",
|
||
|
||
'how to add spot': "Click anywhere on the map to drop a pin at that location! Add photos of the spot, describe the features (rails, ledges, stairs, quarter pipes, etc.), tag the spot type (street, park, diy, etc.), and add access info. Hit publish and boom - it's on Nostr forever! Your contribution helps skaters worldwide find new places to shred. 🛹",
|
||
|
||
'what are spots': "Spots are skateable locations - anywhere you can ride! This includes skateparks (concrete bowls, ramps), street spots (stairs, handrails, ledges), DIY spots (built by skaters), transitions (quarter pipes, half pipes), and bowls. Each spot is geotagged on the map with photos, descriptions, and features. Think of it as Waze for skateboarding - skaters helping skaters find the best places to session! 🗺️",
|
||
|
||
'what are shops': "Skate shops are local businesses where you can buy boards, trucks, wheels, shoes, and all your skate gear. They're tagged with 🏪 on the map. Supporting your local shop supports the skate community! Many shops are run by skaters, host events, and are the heart of local skate scenes. You can add shops to the map too - just make sure to verify ownership if you're claiming a shop profile!",
|
||
|
||
'what is gear swap': "Gear Swap is a peer-to-peer marketplace where skaters can buy and sell used gear at specific spots! See something for sale? Meet at that spot for safe pickup. It's marked with 🛒 on the map. Important: Gear listings DON'T appear at shops (conflict of interest) - they only show at regular skate spots. List your old deck, wheels, or shoes and meet up at your local spot to sell them!",
|
||
|
||
'how to sell gear': "Find a spot on the map and click it, then scroll to the Gear Swap section and hit 'List Gear'. Upload photos, set your price in dollars (or mark as free), describe the condition, and add when you're usually at that spot. Buyers will see your listing when they view that spot. Meet up safely at the spot for the transaction. Keep it skater-to-skater! 💰",
|
||
|
||
'can i list gear at shop': "Nope! Gear Swap is peer-to-peer only and doesn't appear at shop locations to avoid conflicts of interest. Shops sell new gear - Gear Swap is for used gear between skaters. List your gear at regular skate spots (parks, street spots, DIY spots) instead. This keeps shops and the marketplace separate and fair for everyone! 🏪🚫",
|
||
|
||
'how to find shop': "Toggle on the 🏪 Shops filter at the top of the map (or in list view). You'll see all registered skate shops with their locations, contact info, hours, and what they carry. Click any shop to see details or get directions. Support your local shop - they're the backbone of the skate community! Many accept Bitcoin payments too. ⚡",
|
||
|
||
'how to find skate shop': "Toggle on the 🏪 Shops filter at the top of the map (or in list view). You'll see all registered skate shops with their locations, contact info, hours, and what they carry. Click any shop to see details or get directions. Support your local shop - they're the backbone of the skate community! Many accept Bitcoin payments too. ⚡",
|
||
|
||
'how to add shop': "If you're a shop owner: Go to the map and look for 'Add Shop' option. Fill in your business details, upload photos, add your hours and contact info. For verification, set up a NIP-05 identifier on your domain - this proves you own the shop! If you're NOT the owner but want to add a shop to the map, you can still add it, but owners can later claim and verify their profiles.",
|
||
|
||
'what are spot types': "Spots are tagged by their features: 🏞️ Street (stairs, rails, ledges, gaps), 🛝 Park (built skateparks with ramps), 🏗️ DIY (skater-built spots), 🌊 Bowl (concrete bowls and pools), 🎪 Vert (half pipes, mega ramps), 🛤️ Transition (quarter pipes, mini ramps), 🏛️ Plaza (open areas with multiple features), and more! Use these filters to find exactly what you want to skate.",
|
||
|
||
'spot types': "Spots are tagged by their features: 🏞️ Street (stairs, rails, ledges, gaps), 🛝 Park (built skateparks with ramps), 🏗️ DIY (skater-built spots), 🌊 Bowl (concrete bowls and pools), 🎪 Vert (half pipes, mega ramps), 🛤️ Transition (quarter pipes, mini ramps), 🏛️ Plaza (open areas with multiple features), and more! Use these filters to find exactly what you want to skate.",
|
||
|
||
'how to filter spots': "Use the filter buttons at the top! Toggle Spots 🛹, Shops 🏪, and Gear 🛒 on/off to show/hide each type. Below that, you'll see spot type filters like Street, Park, Bowl, DIY, etc. Click any to filter - click again to remove. Switch between Map and List view using the view toggle buttons. In list view, you can search by name or location too!",
|
||
|
||
'weather on map': "Toggle the ☁️ Weather button to see real-time conditions at spots! You'll see temperature, rain probability, wind, and a 'skateability score'. Filter by 'Good' or 'Perfect' conditions to find spots that are dry and rideable right now. Weather doesn't show for shops (since you're shopping indoors). This feature helps you plan sessions and avoid skating in the rain! 🌤️",
|
||
|
||
'map vs list view': "Map view shows spots visually on an interactive map - great for discovering spots near you or planning routes. List view shows all spots in a grid with photos and details - better for browsing and filtering by specific types. Toggle between them with the buttons at the top. Both views respect your filters!",
|
||
};
|
||
|
||
// Context-aware answer finder
|
||
function findAnswer(question: string, context?: SasquatchContext): string {
|
||
const q = question.toLowerCase();
|
||
|
||
// Choose FAQ based on context
|
||
let faq: FAQResponses;
|
||
switch (context) {
|
||
case 'lab':
|
||
faq = LAB_FAQ;
|
||
break;
|
||
case 'map':
|
||
faq = MAP_FAQ;
|
||
break;
|
||
case 'home':
|
||
case 'onboarding':
|
||
default:
|
||
faq = HOME_ONBOARDING_FAQ;
|
||
break;
|
||
}
|
||
|
||
// Check context-specific FAQ first
|
||
for (const [key, answer] of Object.entries(faq)) {
|
||
if (q.includes(key)) {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
// Fallback to checking all FAQs if not found in context
|
||
const allFAQs = { ...HOME_ONBOARDING_FAQ, ...LAB_FAQ, ...MAP_FAQ };
|
||
for (const [key, answer] of Object.entries(allFAQs)) {
|
||
if (q.includes(key)) {
|
||
return answer;
|
||
}
|
||
}
|
||
|
||
return "Hmm, I'm not sure about that one! Try checking the FAQ page, or ask the community on Nostr. You can also browse the map to learn by exploring!";
|
||
}
|
||
|
||
export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
||
const { user } = useCurrentUser();
|
||
const [isMinimized, setIsMinimized] = useLocalStorage('sasquatch-minimized', false);
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
|
||
const [userInput, setUserInput] = useState('');
|
||
const [showChat, setShowChat] = useState(false);
|
||
|
||
useEffect(() => {
|
||
if (message) {
|
||
// Delay appearance for smooth animation when first loading
|
||
const timer = setTimeout(() => setIsVisible(true), 500);
|
||
return () => clearTimeout(timer);
|
||
} else {
|
||
setIsVisible(false);
|
||
}
|
||
}, [message]);
|
||
|
||
const handleDismiss = () => {
|
||
// Just minimize instead of fully dismissing
|
||
setIsMinimized(true);
|
||
};
|
||
|
||
const handleSendMessage = () => {
|
||
if (!userInput.trim()) return;
|
||
|
||
const userMsg: ChatMessage = {
|
||
role: 'user',
|
||
text: userInput,
|
||
timestamp: Date.now(),
|
||
};
|
||
|
||
const answer = findAnswer(userInput, message?.context);
|
||
const sasquatchMsg: ChatMessage = {
|
||
role: 'sasquatch',
|
||
text: answer,
|
||
timestamp: Date.now() + 500,
|
||
};
|
||
|
||
setChatMessages([...chatMessages, userMsg, sasquatchMsg]);
|
||
setUserInput('');
|
||
};
|
||
|
||
// Don't show if no message for this page
|
||
if (!message) return null;
|
||
|
||
return (
|
||
<>
|
||
{/* Minimized button */}
|
||
{isMinimized ? (
|
||
<div
|
||
className={cn(
|
||
'fixed bottom-20 md:bottom-4 left-4 z-40 transition-all duration-300',
|
||
'opacity-100 translate-y-0' // Always visible when minimized
|
||
)}
|
||
>
|
||
<Button
|
||
onClick={() => setIsMinimized(false)}
|
||
className="flex items-center gap-2 bg-gradient-to-r from-amber-700 to-amber-900 hover:from-amber-600 hover:to-amber-800 text-white shadow-lg h-12 px-4 border-2 border-amber-500/30"
|
||
>
|
||
<img
|
||
src="/images/sasquatch-guide.webp"
|
||
alt="The Sasquatch"
|
||
className="h-6 w-6 rounded-full"
|
||
/>
|
||
<HelpCircle className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
) : (
|
||
/* Full guide */
|
||
<div
|
||
className={cn(
|
||
'fixed bottom-20 md:bottom-4 left-4 right-4 md:right-auto z-40 transition-all duration-300',
|
||
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'
|
||
)}
|
||
>
|
||
<div className="bg-gradient-to-br from-amber-50 to-orange-50 border-2 border-amber-500/50 rounded-lg shadow-2xl max-w-sm mx-auto md:mx-0">
|
||
{/* Header with Sasquatch */}
|
||
<div className="flex items-start gap-3 p-4 pb-3 border-b border-amber-200">
|
||
<div className="relative">
|
||
<img
|
||
src="/images/sasquatch-guide.webp"
|
||
alt="The Sasquatch"
|
||
className="h-12 w-12 rounded-full border-2 border-amber-400 shadow-md"
|
||
/>
|
||
<span className="absolute -bottom-1 -right-1 flex h-4 w-4">
|
||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
|
||
<span className="relative inline-flex rounded-full h-4 w-4 bg-green-500 border-2 border-white" />
|
||
</span>
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<div className="font-bold text-sm text-amber-900 flex items-center gap-2">
|
||
{message.title}
|
||
<span className="text-xs px-1.5 py-0.5 bg-amber-200 text-amber-800 rounded-full">
|
||
🛹
|
||
</span>
|
||
</div>
|
||
<div className="text-xs text-amber-700">The Sasquatch</div>
|
||
</div>
|
||
<div className="flex gap-1">
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-6 w-6 text-amber-600 hover:text-amber-800 hover:bg-amber-100"
|
||
onClick={() => setIsMinimized(true)}
|
||
>
|
||
<Minimize2 className="h-3 w-3" />
|
||
</Button>
|
||
<Button
|
||
variant="ghost"
|
||
size="icon"
|
||
className="h-6 w-6 text-amber-600 hover:text-red-600 hover:bg-red-50"
|
||
onClick={handleDismiss}
|
||
>
|
||
<X className="h-3 w-3" />
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Message */}
|
||
<div className="p-4">
|
||
<p className="text-sm text-gray-800 leading-relaxed">
|
||
{message.message}
|
||
</p>
|
||
|
||
{/* Action button */}
|
||
{message.action && (
|
||
<Button
|
||
onClick={message.action.onClick}
|
||
size="sm"
|
||
className="mt-3 w-full bg-gradient-to-r from-amber-600 to-orange-600 hover:from-amber-700 hover:to-orange-700 text-white font-bold"
|
||
>
|
||
{message.action.label}
|
||
</Button>
|
||
)}
|
||
|
||
{/* Chat feature */}
|
||
{message.enableChat && (
|
||
<div className="mt-4 pt-4 border-t border-amber-200">
|
||
{!showChat ? (
|
||
<Button
|
||
onClick={() => setShowChat(true)}
|
||
size="sm"
|
||
variant="outline"
|
||
className="w-full border-amber-400 text-amber-800 hover:bg-amber-100"
|
||
>
|
||
<HelpCircle className="h-3 w-3 mr-2" />
|
||
{message.context === 'lab' && 'Ask about Coaching & Filming'}
|
||
{message.context === 'map' && 'Ask about Spots, Shops & Gear'}
|
||
{(message.context === 'home' || message.context === 'onboarding') && 'Ask me about Nostr, Bitcoin & Zaps'}
|
||
</Button>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{/* Default questions */}
|
||
{chatMessages.length === 0 && message.suggestedQuestions && (
|
||
<div className="space-y-1.5">
|
||
<div className="text-[10px] text-amber-700 font-semibold mb-2">Quick Questions:</div>
|
||
{message.suggestedQuestions.map((q) => (
|
||
<Button
|
||
key={q}
|
||
onClick={() => {
|
||
setUserInput(q);
|
||
setTimeout(() => handleSendMessage(), 100);
|
||
}}
|
||
size="sm"
|
||
variant="ghost"
|
||
className="w-full justify-start text-xs text-amber-800 hover:bg-amber-100 h-auto py-1.5"
|
||
>
|
||
{q}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
)}
|
||
|
||
{/* Chat messages */}
|
||
{chatMessages.length > 0 && (
|
||
<ScrollArea className="h-48 rounded border border-amber-200 bg-white p-2">
|
||
<div className="space-y-2">
|
||
{chatMessages.map((msg, idx) => (
|
||
<div
|
||
key={idx}
|
||
className={cn(
|
||
'text-xs p-2 rounded leading-relaxed',
|
||
msg.role === 'user'
|
||
? 'bg-blue-100 text-blue-900 ml-6'
|
||
: 'bg-amber-100 text-amber-900 mr-6'
|
||
)}
|
||
>
|
||
<div className="font-bold text-[10px] mb-1 opacity-70">
|
||
{msg.role === 'user' ? '🧑 You' : '🦧 Sasquatch'}
|
||
</div>
|
||
{msg.text}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</ScrollArea>
|
||
)}
|
||
|
||
{/* Input */}
|
||
<div className="flex gap-2">
|
||
<Input
|
||
value={userInput}
|
||
onChange={(e) => setUserInput(e.target.value)}
|
||
onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
|
||
placeholder="Type your question..."
|
||
className="text-sm border-amber-300 focus-visible:ring-amber-400"
|
||
/>
|
||
<Button
|
||
onClick={handleSendMessage}
|
||
size="sm"
|
||
disabled={!userInput.trim()}
|
||
className="bg-amber-600 hover:bg-amber-700"
|
||
>
|
||
<Send className="h-3 w-3" />
|
||
</Button>
|
||
</div>
|
||
|
||
{chatMessages.length > 0 && (
|
||
<Button
|
||
onClick={() => {
|
||
setChatMessages([]);
|
||
setShowChat(false);
|
||
}}
|
||
size="sm"
|
||
variant="ghost"
|
||
className="w-full text-xs text-amber-600 hover:text-amber-800"
|
||
>
|
||
Start over
|
||
</Button>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</>
|
||
);
|
||
}
|