"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { MANUAL_INTRO, MANUAL_PATHS_HEADING, MANUAL_TITLE, PARTICIPATION_MANUAL, PARTICIPATION_PATHS, isManualLanguage, type ManualLanguage, } from "@tactical-media-broadcast-hub/core"; import { Logo } from "../components/brand/logo"; import { AuthProvider, useAuth } from "../lib/auth-store"; /** * Public participation-manual landing page (the app root). * * Design (docs/design/2026-10-12-collectives-architecture.md §"Participation * manual"): the root explains the platform in plain language and offers the * three participation paths — join a collective, start one, work on your own — * before any auth wall. The copy is the SINGLE source in * packages/core/src/participation-manual.ts: the provisioner posts the * byte-identical text into onboarding rooms, so this page and the bot guide can * never drift apart. * * Spanish is the primary language: the page opens in ES and the EN toggle is a * one-tap switch, mirroring the bot's ES-first posting order. */ const STORAGE_KEY = "tmbh.manual.lang"; function readStoredLanguage(): ManualLanguage { if (typeof window === "undefined") return "es"; try { const stored = window.localStorage.getItem(STORAGE_KEY); return isManualLanguage(stored) ? stored : "es"; } catch { return "es"; } } /** Renders manual body text: blank-line paragraphs, "- " bullets. */ function ManualBody({ text }: { text: string }): React.ReactElement { const blocks = text.split("\n\n"); return ( <> {blocks.map((block, index) => { const lines = block.split("\n"); return lines.every((line) => line.startsWith("- ")) ? ( ) : (

{block}

); })} ); } /** * The root sits OUTSIDE the (main) route group, so its AuthProvider must be * local: the (main) layout's provider does not wrap this page, and useAuth * without a provider throws during prerender. */ export default function ManualLandingPage(): React.ReactElement { return ( ); } function ManualLanding(): React.ReactElement { const { status, session } = useAuth(); const router = useRouter(); const [lang, setLang] = useState("es"); useEffect(() => setLang(readStoredLanguage()), []); function switchLanguage(next: ManualLanguage): void { setLang(next); try { window.localStorage.setItem(STORAGE_KEY, next); } catch { // Private-browsing refusal: the choice still holds for this view. } } // Signed-in visitors go straight to the app; the manual stays reachable at /wiki. useEffect(() => { if (status === "authenticated") router.replace("/streams"); }, [status, router]); const t = (text: T): string => text[lang]; return (
Tactical Media Broadcast Hub
{(["es", "en"] as const).map((code) => ( ))}
{status === "authenticated" ? t({ es: "Abrir la app", en: "Open app" }) : t({ es: "Entrar", en: "Sign in" })}

{t(MANUAL_TITLE)}

{t(MANUAL_INTRO)}

{/* The three participation paths — the core of the page. */}

{t(MANUAL_PATHS_HEADING)}

    {PARTICIPATION_PATHS.map((path) => { // All three land on /onboarding: that page holds the sign-in and // create-account forms. A join link itself arrives from outside // (the only /join route is /join/[token]); starting/solo need the // same forms, so one destination is honest for all three. const href = "/onboarding"; return (
  • {t(path.title)} {t(path.summary)} {t(path.action)} ›
  • ); })}
{/* The manual itself. */}
{PARTICIPATION_MANUAL.map((section, index) => (

{String(index + 1).padStart(2, "0")} {t(section.heading)}

))}
); }