import { useLayoutEffect, useRef, useState, type FormEvent, type KeyboardEvent, } from "react"; import { ArrowUp, Check, Globe, Loader2, Sparkles } from "lucide-react"; import { FREE_ONBOARDING_QUESTION_LIMIT } from "@/shared/onboardingChat"; const DISCORD_URL = "https://discord.gg/c9uGs3cFXr"; export function SuggestedQuestions({ questions, primaryQuestions = [], onSelect, }: { questions: string[]; primaryQuestions?: string[]; onSelect: (question: string) => void; }) { return (
{questions.map((question) => primaryQuestions.includes(question) ? ( ) : ( ), )}
); } export function WelcomeMessage({ domain, checkoutError, isStartingCheckout, onUpgrade, }: { domain: string; checkoutError: string | null; isStartingCheckout: boolean; onUpgrade: () => void; }) { return (

Hey, I’m Sam — welcome to OpenSEO.

To get full access to OpenSEO, you need to upgrade to the paid plan. But, I’m here if you have any questions.

You can also{" "} join the Discord {" "} or email{" "} ben@openseo.so {" "} if you have any questions I can’t help you with.

Want me to analyze{" "} {domain} and draft a strategy, or do you have questions first? Pick one below to get started.

Want Sam to keep going?

Upgrade to run keyword research, rank tracking, and site audits on{" "} {domain}.

{checkoutError ? (

{checkoutError}

) : null}
); } // Left-rail upgrade CTA. Hidden below `lg` (the inline callout + remaining // hint cover narrow viewports). export function UpgradeSidebar({ domain, questionsUsed, isStartingCheckout, onUpgrade, }: { domain: string; questionsUsed: number; isStartingCheckout: boolean; onUpgrade: () => void; }) { const features = [ "Keyword research, backlinks, rank tracking & site audits", "Google Search Console — read-only, no credits, no Google Cloud setup", "Connect Claude, Cursor, Codex & other MCP clients", "Top-up credits roll over and never expire", ]; const used = Math.min(questionsUsed, FREE_ONBOARDING_QUESTION_LIMIT); const progress = (used / FREE_ONBOARDING_QUESTION_LIMIT) * 100; return ( ); } // Replaces the composer once a free user exhausts their question allowance. export function ChatGate({ isStartingCheckout, onUpgrade, }: { isStartingCheckout: boolean; onUpgrade: () => void; }) { return (

That’s all {FREE_ONBOARDING_QUESTION_LIMIT} free questions

Upgrade to keep working with Sam and unlock the full OpenSEO app.

30-day money-back guarantee

); } export function ChatComposer({ busy, onSend, }: { busy: boolean; onSend: (text: string) => void; }) { const [value, setValue] = useState(""); const textareaRef = useRef(null); // Auto-grow the textarea up to a few lines, then scroll. Resetting height to // `auto` first lets it shrink as well as grow. useLayoutEffect(() => { const ta = textareaRef.current; if (!ta) return; ta.style.height = "auto"; ta.style.height = `${Math.min(ta.scrollHeight, 160)}px`; }, [value]); function submit() { const text = value.trim(); if (!text || busy) return; onSend(text); setValue(""); } function handleSubmit(event: FormEvent) { event.preventDefault(); submit(); } function handleKey(event: KeyboardEvent) { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); submit(); } } return (