import { useQuery } from '@tanstack/react-query'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import { PublicComments } from '../comments/CommentsSection'; import { ApiError, apiGet } from '../lib/api'; import { useDocumentTitle } from '../lib/use-document-title'; import { countWords, htmlToText } from '../lib/word-count'; import { NotFoundPage } from './NotFoundPage'; import { PageStatusBar } from './PageStatusBar'; interface PublicPageContent { pondName: string; pondSlug: string; title: string; slug: string; html: string; updatedAt: string; } /** * Read-only public page view (issue #56): renders a page that a `public` grant * opens to anonymous visitors, from the server-derived HTML — deliberately * WITHOUT importing the collaborative editor, so anonymous readers never load * the editor bundle. A non-public page 404s (the api hides its existence). */ export function PublicPageView(): React.JSX.Element { const { t } = useTranslation('public'); const { pondSlug = '', pageSlug = '' } = useParams<{ pondSlug: string; pageSlug: string }>(); const query = useQuery({ queryKey: ['public-page', pondSlug, pageSlug], queryFn: () => apiGet(`/public/${pondSlug}/${pageSlug}/content`), enabled: Boolean(pondSlug && pageSlug), retry: false, }); useDocumentTitle(query.data?.title, query.data?.pondName); // Word count for the status line (#134), derived from the server-rendered // HTML — no editor bundle needed. Kept before the early returns so the hook // order stays stable. const wordCount = useMemo( () => countWords(htmlToText(query.data?.html ?? '')), [query.data?.html], ); if (query.error instanceof ApiError && query.error.status === 404) return ; if (query.isLoading || !query.data) return
; const page = query.data; return (

{t('readOnlyBadge')}

{page.pondName}

{page.title}

{/* The HTML comes from the server's content cache (issue #24), derived from the sanitized editor schema — safe to render. */}
{/* Existing comments, read-only for anonymous visitors (issue #133). */}
); }