v1.5 - history and timeline as well as many datastructure updates added, polished, fixes
This commit is contained in:
parent
1f0aa3078f
commit
1d84400aef
63 changed files with 7927 additions and 208 deletions
182
src/pages/admin/AdminHome.tsx
Normal file
182
src/pages/admin/AdminHome.tsx
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/* ═══════════════════════════════════════════════════════════════
|
||||
ADMIN HOME
|
||||
|
||||
Where login lands. Two columns: the cards are the navigation —
|
||||
the header deliberately drops its tab row here so the same links
|
||||
aren't drawn twice — and a standing info panel on the right.
|
||||
|
||||
The cards come from adminNav.js, the same list the CMS header
|
||||
reads, so a new entity shows up here the moment it's registered.
|
||||
Forms sit in their own block below: they're submissions coming
|
||||
in rather than content going out, and there'll be more of them
|
||||
than the feedback queue eventually. The Panel block below that
|
||||
only exists for superadmins.
|
||||
|
||||
The right-hand panel is deliberately inert. Nothing here
|
||||
fetches, so the landing page can't be slow or half-broken on
|
||||
arrival; anything live (open feedback count, last-edited
|
||||
record) wants to be a separate component that fails on its own.
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import { useAuth } from "../../lib/auth.tsx";
|
||||
import { SITE_VERSION } from "../../lib/version.ts";
|
||||
import { ROLE_LABELS, isSuper } from "../../lib/roles.ts";
|
||||
import { CMS_NAV, FORMS_NAV, PANEL_NAV, target } from "./adminNav.js";
|
||||
|
||||
/* One card. The title link is stretched over the whole card with
|
||||
`after:absolute`, which makes the card clickable without nesting
|
||||
an anchor inside an anchor; the sub-links sit above it on z-10 so
|
||||
they stay separately clickable. */
|
||||
function NavCard({ item }) {
|
||||
const to = target(item);
|
||||
|
||||
// Drop the child that just repeats the card's own destination —
|
||||
// "All organizations" under Organizations.
|
||||
const extras = (item.children ?? []).filter((child) => child.to !== to);
|
||||
|
||||
return (
|
||||
<div className="group relative flex flex-col rounded-2xl border border-[#138ba0]/20 bg-white p-5 transition-all hover:border-[#138ba0]/60 hover:shadow-sm">
|
||||
<div className="flex items-baseline gap-3">
|
||||
<h3 className="text-base font-semibold text-[#138ba0]">
|
||||
<Link
|
||||
to={to}
|
||||
className="after:absolute after:inset-0 after:rounded-2xl after:content-['']"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
</h3>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="ml-auto text-[#138ba0] opacity-0 transition-opacity group-hover:opacity-100"
|
||||
>
|
||||
→
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{item.blurb && (
|
||||
<p className="mt-1.5 text-sm leading-relaxed text-[#4a6b72]">{item.blurb}</p>
|
||||
)}
|
||||
|
||||
{extras.length > 0 && (
|
||||
<div className="relative z-10 mt-4 flex flex-wrap gap-x-4 gap-y-1 border-t border-[#138ba0]/10 pt-3 text-sm">
|
||||
{extras.map((child) => (
|
||||
<Link
|
||||
key={child.to}
|
||||
to={child.to}
|
||||
className="text-[#4a6b72] underline-offset-4 transition-colors hover:text-[#138ba0] hover:underline"
|
||||
>
|
||||
{child.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CardBlock({ title, blurb, children }) {
|
||||
return (
|
||||
<div className="mt-10">
|
||||
<div className="flex items-baseline gap-3">
|
||||
<h2 className="text-lg font-semibold text-[#138ba0]">{title}</h2>
|
||||
{blurb && <p className="text-sm text-[#4a6b72]">{blurb}</p>}
|
||||
</div>
|
||||
<div className="mt-4 grid gap-4 sm:grid-cols-2">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PanelSection({ title, children }) {
|
||||
return (
|
||||
<section className="border-b border-[#138ba0]/10 px-5 py-4 last:border-b-0">
|
||||
<h2 className="text-xs font-semibold uppercase tracking-wider text-[#4a6b72]/70">
|
||||
{title}
|
||||
</h2>
|
||||
<div className="mt-2.5 text-sm text-[#4a6b72]">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const QUICK_ADD = [
|
||||
{ to: "/admin/events/new", label: "New event" },
|
||||
{ to: "/admin/people/new", label: "New person" },
|
||||
{ to: "/admin/organizations/new", label: "New organization" },
|
||||
{ to: "/admin/timeline/new", label: "New timeline entry" },
|
||||
];
|
||||
|
||||
export default function AdminHome() {
|
||||
const { user } = useAuth();
|
||||
const role = ROLE_LABELS[user?.role] ?? user?.role;
|
||||
|
||||
return (
|
||||
<div className="grid gap-8 lg:grid-cols-[1fr_17rem] lg:items-start">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-[#138ba0]">
|
||||
{user?.name ? `Welcome back, ${user.name.split(" ")[0]}.` : "Welcome back."}
|
||||
</h1>
|
||||
<p className="mt-1 text-[#4a6b72]">Pick a section to work in.</p>
|
||||
|
||||
<div className="mt-6 grid gap-4 sm:grid-cols-2">
|
||||
{CMS_NAV.map((item) => (
|
||||
<NavCard key={item.label} item={item} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<CardBlock title={FORMS_NAV.label} blurb={FORMS_NAV.blurb}>
|
||||
{FORMS_NAV.children.map((form) => (
|
||||
<NavCard key={form.to} item={form} />
|
||||
))}
|
||||
</CardBlock>
|
||||
|
||||
{isSuper(user) && (
|
||||
<CardBlock title="Superadmin" blurb="Only superadmins see this block.">
|
||||
<NavCard item={PANEL_NAV} />
|
||||
</CardBlock>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sticky so it stays put once the card column outgrows it. */}
|
||||
<aside className="divide-y divide-[#138ba0]/10 rounded-2xl border border-[#138ba0]/20 bg-white lg:sticky lg:top-6">
|
||||
<PanelSection title="Signed in">
|
||||
<p className="font-medium text-[#0f2f36]">{user?.name || user?.email}</p>
|
||||
{user?.name && user?.email && (
|
||||
<p className="mt-0.5 break-all text-xs text-[#4a6b72]/80">{user.email}</p>
|
||||
)}
|
||||
{role && (
|
||||
<span className="mt-2 inline-block rounded-full bg-[#eef9fb] px-2.5 py-0.5 text-xs font-medium text-[#138ba0]">
|
||||
{role}
|
||||
</span>
|
||||
)}
|
||||
</PanelSection>
|
||||
|
||||
<PanelSection title="Start something">
|
||||
<ul className="space-y-1.5">
|
||||
{QUICK_ADD.map((link) => (
|
||||
<li key={link.to}>
|
||||
<Link
|
||||
to={link.to}
|
||||
className="underline-offset-4 transition-colors hover:text-[#138ba0] hover:underline"
|
||||
>
|
||||
{link.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</PanelSection>
|
||||
|
||||
<PanelSection title="Site">
|
||||
<p className="font-mono text-xs text-[#4a6b72]/80">{SITE_VERSION}</p>
|
||||
<a
|
||||
href="/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="mt-2 inline-block underline-offset-4 transition-colors hover:text-[#138ba0] hover:underline"
|
||||
>
|
||||
View the public site ↗
|
||||
</a>
|
||||
</PanelSection>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue