diff --git a/frontend/app/explore/layout.tsx b/frontend/app/explore/layout.tsx new file mode 100644 index 0000000..081f266 --- /dev/null +++ b/frontend/app/explore/layout.tsx @@ -0,0 +1,10 @@ +import { PublicHeader } from "@/components/public-header"; + +export default function ExploreLayout({ children }: { children: React.ReactNode }) { + return ( +
+ +
{children}
+
+ ); +} diff --git a/frontend/app/explore/page.tsx b/frontend/app/explore/page.tsx new file mode 100644 index 0000000..53e53e2 --- /dev/null +++ b/frontend/app/explore/page.tsx @@ -0,0 +1,78 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +import { api } from "@/lib/api/client"; +import type { components } from "@/lib/api/schema"; +import { Card, CardContent } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; + +type Tree = components["schemas"]["PublicTreeRead"]; + +// Public directory of trees. The backend returns `public` to everyone and adds +// `site_members` trees when the request carries a valid session — so signed-in +// users see more here without any client-side branching. +export default function ExplorePage() { + const [trees, setTrees] = useState([]); + const [search, setSearch] = useState(""); + const [ready, setReady] = useState(false); + + useEffect(() => { + const q = search.trim(); + const t = setTimeout(async () => { + const { data } = await api.GET("/api/v1/public/trees", { + params: { query: q ? { q } : {} }, + }); + setTrees(data ?? []); + setReady(true); + }, 200); + return () => clearTimeout(t); + }, [search]); + + return ( +
+
+

Explore public trees

+

+ Browse family trees shared on this site. Living people are always hidden. +

+
+ + setSearch(e.target.value)} + /> + + {!ready ? ( +

Loading…

+ ) : trees.length === 0 ? ( +

No public trees{search.trim() ? " match that search" : " yet"}.

+ ) : ( + + )} +
+ ); +} diff --git a/frontend/app/p/layout.tsx b/frontend/app/p/layout.tsx index 571d26b..e03a460 100644 --- a/frontend/app/p/layout.tsx +++ b/frontend/app/p/layout.tsx @@ -1,20 +1,10 @@ -import Link from "next/link"; +import { PublicHeader } from "@/components/public-header"; // Public viewing surface — no auth, no app sidebar. A slim header only. export default function PublicLayout({ children }: { children: React.ReactNode }) { return (
-
-
- - {/* eslint-disable-next-line @next/next/no-img-element */} - Provenance - - - Sign in - -
-
+
{children}
); diff --git a/frontend/components/app-sidebar.tsx b/frontend/components/app-sidebar.tsx index aeb662a..2f4a96d 100644 --- a/frontend/components/app-sidebar.tsx +++ b/frontend/components/app-sidebar.tsx @@ -4,6 +4,7 @@ import { Archive, ArrowDownUp, BookText, + Compass, FolderTree, Image as ImageIcon, LogOut, @@ -92,6 +93,7 @@ export function AppSidebar({ onNavigate }: { onNavigate?: () => void }) { + {treeId && ( diff --git a/frontend/components/public-header.tsx b/frontend/components/public-header.tsx new file mode 100644 index 0000000..ba8361b --- /dev/null +++ b/frontend/components/public-header.tsx @@ -0,0 +1,23 @@ +import Link from "next/link"; + +// Slim header for the public (no-auth) surface: /p/* and /explore. +export function PublicHeader() { + return ( +
+
+ + {/* eslint-disable-next-line @next/next/no-img-element */} + Provenance + + +
+
+ ); +}