From 95e84ad84299bedc4ee6a2e587b29f1c4ff41375 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 21:51:56 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/settings.public-pages.index.tsx | 163 +++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 src/routes/settings.public-pages.index.tsx diff --git a/src/routes/settings.public-pages.index.tsx b/src/routes/settings.public-pages.index.tsx new file mode 100644 index 0000000..db2d4e7 --- /dev/null +++ b/src/routes/settings.public-pages.index.tsx @@ -0,0 +1,163 @@ +import { createFileRoute, Link, useNavigate } from "@tanstack/react-router"; +import { useEffect, useState } from "react"; +import { supabase } from "@/integrations/supabase/client"; +import { useAuth } from "@/lib/auth"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { + Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, +} from "@/components/ui/dialog"; +import { Loader2, Plus, ExternalLink, Pencil, Trash2, FileText } from "lucide-react"; +import { toast } from "sonner"; + +export const Route = createFileRoute("/settings/public-pages/")({ + component: PublicPagesSettingsPage, +}); + +type PageRow = { + id: string; + slug: string; + title: string; + status: string; + show_in_nav: boolean; + sort_order: number; + updated_at: string; +}; + +function PublicPagesSettingsPage() { + const { isAdmin } = useAuth(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(true); + const [pages, setPages] = useState([]); + const [newOpen, setNewOpen] = useState(false); + const [newTitle, setNewTitle] = useState(""); + const [newSlug, setNewSlug] = useState(""); + const [creating, setCreating] = useState(false); + + const load = async () => { + setLoading(true); + const { data } = await supabase + .from("public_pages") + .select("id,slug,title,status,show_in_nav,sort_order,updated_at") + .order("sort_order", { ascending: true }) + .order("title", { ascending: true }); + setPages((data ?? []) as PageRow[]); + setLoading(false); + }; + + useEffect(() => { load(); }, []); + + const slugify = (s: string) => + s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); + + const create = async () => { + if (!newTitle.trim()) { toast.error("Title is required"); return; } + const slug = (newSlug.trim() || slugify(newTitle)).toLowerCase(); + if (!slug) { toast.error("Invalid slug"); return; } + setCreating(true); + const { data, error } = await supabase + .from("public_pages") + .insert({ title: newTitle.trim(), slug, status: "draft" }) + .select("id") + .single(); + setCreating(false); + if (error) { toast.error("Could not create", { description: error.message }); return; } + toast.success("Page created"); + setNewOpen(false); + setNewTitle(""); setNewSlug(""); + navigate({ to: "/settings/public-pages/$pageId", params: { pageId: data.id } }); + }; + + const remove = async (id: string) => { + if (!confirm("Delete this page and all its sections?")) return; + const { error } = await supabase.from("public_pages").delete().eq("id", id); + if (error) { toast.error("Could not delete", { description: error.message }); return; } + toast.success("Deleted"); + load(); + }; + + if (!isAdmin) return

Admins only.

; + + return ( +
+
+
+

Public pages

+

+ Manage marketing pages rendered at /p/{slug}. +

+
+ +
+ + + Pages + + {loading ? ( +
+ ) : pages.length === 0 ? ( +
+ + No pages yet. Create your first one. +
+ ) : ( +
+ {pages.map((p) => ( +
+
+
+ {p.title} + {p.status} + {!p.show_in_nav && hidden in nav} +
+
/p/{p.slug}
+
+
+ {p.status === "published" && ( + + )} + + +
+
+ ))} +
+ )} +
+
+ + + + New public page +
+
+ + { + setNewTitle(e.target.value); + if (!newSlug) setNewSlug(slugify(e.target.value)); + }} placeholder="About Us" /> +
+
+ + setNewSlug(slugify(e.target.value))} placeholder="about-us" /> +

URL: /p/{newSlug || "your-slug"}

+
+
+ + + + +
+
+
+ ); +}