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 (
);
}