diff --git a/src/components/SasquatchGuide.tsx b/src/components/SasquatchGuide.tsx index b241206..89aadb6 100644 --- a/src/components/SasquatchGuide.tsx +++ b/src/components/SasquatchGuide.tsx @@ -961,7 +961,9 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) { let usedCVM = false; let liveTs = 0; // timestamp of the streaming bubble (0 = none) let streamSuppressed = false; // output looks like a JSON tool call β€” never render it - try { + // AI answers are a Pro feature. Free users skip this entirely and fall + // through to the static FAQ, glossary and local relay lookups below. + if (isPro) try { // The system prompt goes in a real `system` message rather than being // smuggled into a user turn β€” the model then treats the relay/derived // text below as lower-trust input, blunting prompt-injection via event @@ -1340,7 +1342,10 @@ Instructions: const fallback = staticAnswer ?? answerFromGlossary(question) ?? - `My AI brain's catching some air right now 🦧 β€” but I've still got you:\n\nβ€’ πŸ—ΊοΈ [Map](/) β€” spots, shops, events near you\nβ€’ πŸ§ͺ [The Lab](/lab) β€” coaches, filmers, editors\nβ€’ πŸ“– [Glossary](/glossary) β€” 150+ tricks & terms\nβ€’ πŸ‘₯ [Crews](/crews) β€” find your people\n\nOr just tell me a **city** ("spots in Barcelona") or a **trick** ("what's a nollie") and I'll answer straight up. πŸ›Ή`; + (!isPro + ? // Free tier: no AI. Nudge to Pro, but point at what still works. + "That one needs AI reasoning β€” a **Sasquatch Pro** feature ⚑🦧\n\nI can still find **spots, events, coaches, crews, shops, tours and gear** straight from the relays β€” ask me e.g. \"spots in Barcelona\" β€” and the [Glossary](/glossary) covers 150+ tricks & terms. Tap **⚑ Go Pro** up top to unlock AI answers." + : `My AI brain's catching some air right now 🦧 β€” but I've still got you:\n\nβ€’ πŸ—ΊοΈ [Map](/) β€” spots, shops, events near you\nβ€’ πŸ§ͺ [The Lab](/lab) β€” coaches, filmers, editors\nβ€’ πŸ“– [Glossary](/glossary) β€” 150+ tricks & terms\nβ€’ πŸ‘₯ [Crews](/crews) β€” find your people\n\nOr just tell me a **city** ("spots in Barcelona") or a **trick** ("what's a nollie") and I'll answer straight up. πŸ›Ή`); appendMessage({ role: 'sasquatch', text: fallback, timestamp: Date.now() }); } } catch (err) { diff --git a/src/hooks/useSasquatchPro.ts b/src/hooks/useSasquatchPro.ts index 4bacc7d..d1dbe04 100644 --- a/src/hooks/useSasquatchPro.ts +++ b/src/hooks/useSasquatchPro.ts @@ -30,7 +30,7 @@ import { useCurrentUser } from '@/hooks/useCurrentUser'; import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions'; import { getZapProviderPubkey } from '@/lib/lightningAddress'; import { getSatoshisFromBolt11 } from '@/lib/bolt11'; -import { PRO_LIGHTNING_ADDRESS, PRO_TIERS } from '@/lib/sasquatchPro'; +import { PRO_LIGHTNING_ADDRESS, PRO_TIERS, DAY_SECONDS } from '@/lib/sasquatchPro'; import type { NostrEvent } from '@nostrify/nostrify'; const PLATFORM_PUBKEY = PLATFORM_OWNER_PUBKEYS[0]; @@ -103,8 +103,13 @@ function expiryFromZapReceipt( } if (paidSats < MIN_PRO_SATS) return null; - const exp = Number(tagVal(zapReq.tags, 'pro-expiry')); - return Number.isFinite(exp) && exp > 0 ? exp : null; + // Enforce the tier from what was ACTUALLY paid. Never trust the user-signed + // `pro-expiry` tag β€” that let a 7-day payment self-assert a year. Pick the + // largest tier the payment covers and run the pass from when the receipt was + // issued (so paying later genuinely extends access). + const tier = [...PRO_TIERS].sort((a, b) => b.sats - a.sats).find((t) => paidSats >= t.sats); + if (!tier) return null; + return receipt.created_at + tier.days * DAY_SECONDS; } export function useSasquatchPro(): ProStatus {