@ampless/admin 0.2.0-alpha.5 → 0.2.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
2
2
  import { Admin } from '../index.js';
3
3
  import 'ampless';
4
4
  import '@ampless/runtime';
5
- import '../i18n-ByHM_Bho.js';
5
+ import '../i18n-Bc4SYgWx.js';
6
6
  import '@aws-amplify/adapter-nextjs';
7
7
 
8
8
  /**
@@ -4,7 +4,7 @@ import {
4
4
  publicMediaUrl,
5
5
  setAdminMediaContext,
6
6
  translate
7
- } from "./chunk-FI7CM4LH.js";
7
+ } from "./chunk-L5NHN3MY.js";
8
8
  import {
9
9
  invalidateSiteSettingsCache
10
10
  } from "./chunk-VXEVLHGL.js";
@@ -82,6 +82,18 @@ function decodeBody(value) {
82
82
  return value;
83
83
  }
84
84
  }
85
+ function decodeMetadata(value) {
86
+ if (value === null || value === void 0) return void 0;
87
+ const parsed = typeof value === "string" ? safeJsonParse(value) : value;
88
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
89
+ }
90
+ function safeJsonParse(value) {
91
+ try {
92
+ return JSON.parse(value);
93
+ } catch {
94
+ return value;
95
+ }
96
+ }
85
97
  function toCorePost(p) {
86
98
  return {
87
99
  postId: p.postId,
@@ -93,7 +105,8 @@ function toCorePost(p) {
93
105
  body: decodeBody(p.body),
94
106
  status: p.status ?? "draft",
95
107
  publishedAt: p.publishedAt ?? void 0,
96
- tags: (p.tags ?? []).filter((t) => typeof t === "string")
108
+ tags: (p.tags ?? []).filter((t) => typeof t === "string"),
109
+ metadata: decodeMetadata(p.metadata)
97
110
  };
98
111
  }
99
112
  function postTagEntries(post) {
@@ -190,6 +203,7 @@ function installAdminPostsProvider() {
190
203
  status: input.status,
191
204
  publishedAt: input.publishedAt,
192
205
  tags: input.tags,
206
+ ...input.metadata !== void 0 && { metadata: encodeBody(input.metadata) },
193
207
  // Denormalized GSI keys. Must match every change to slug /
194
208
  // status — see the update() branch below.
195
209
  siteIdStatus: composeSiteIdStatus(input.siteId, input.status),
@@ -216,6 +230,7 @@ function installAdminPostsProvider() {
216
230
  ...patch.status !== void 0 && { status: patch.status },
217
231
  ...patch.publishedAt !== void 0 && { publishedAt: patch.publishedAt },
218
232
  ...patch.tags !== void 0 && { tags: patch.tags },
233
+ ...patch.metadata !== void 0 && { metadata: encodeBody(patch.metadata) },
219
234
  ...patch.status !== void 0 && nextStatus && { siteIdStatus: composeSiteIdStatus(siteId, nextStatus) },
220
235
  ...patch.slug !== void 0 && nextSlug && { siteIdSlug: composeSiteIdSlug(siteId, nextSlug) }
221
236
  });
@@ -351,12 +366,12 @@ function AdminDashboard() {
351
366
  }, []);
352
367
  const published = posts.filter((p) => p.status === "published").length;
353
368
  const drafts = posts.filter((p) => p.status === "draft").length;
354
- return /* @__PURE__ */ jsxs("div", { className: "p-8", children: [
355
- /* @__PURE__ */ jsxs("div", { className: "mb-8 flex items-center justify-between", children: [
356
- /* @__PURE__ */ jsx3("h1", { className: "text-3xl font-bold", children: t("dashboard.title") }),
369
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
370
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
371
+ /* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold md:text-3xl", children: t("dashboard.title") }),
357
372
  /* @__PURE__ */ jsx3(Button, { asChild: true, children: /* @__PURE__ */ jsx3(Link, { href: "/admin/posts/new", children: t("dashboard.newPost") }) })
358
373
  ] }),
359
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [
374
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: [
360
375
  /* @__PURE__ */ jsxs(Card, { children: [
361
376
  /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.totalPosts") }) }),
362
377
  /* @__PURE__ */ jsxs(CardContent, { children: [
@@ -398,15 +413,15 @@ function PostsList() {
398
413
  const siteId = readAdminSiteIdFromCookie();
399
414
  listPosts2({ status: "all", siteId }).then(setPosts).finally(() => setLoading(false));
400
415
  }, []);
401
- return /* @__PURE__ */ jsxs2("div", { className: "p-8", children: [
402
- /* @__PURE__ */ jsxs2("div", { className: "mb-8 flex items-center justify-between", children: [
403
- /* @__PURE__ */ jsx4("h1", { className: "text-3xl font-bold", children: t("posts.list.title") }),
416
+ return /* @__PURE__ */ jsxs2("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
417
+ /* @__PURE__ */ jsxs2("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: [
418
+ /* @__PURE__ */ jsx4("h1", { className: "text-2xl font-bold md:text-3xl", children: t("posts.list.title") }),
404
419
  /* @__PURE__ */ jsx4(Button2, { asChild: true, children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.newButton") }) })
405
420
  ] }),
406
421
  loading ? /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("common.loading") }) : posts.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "rounded-md border p-12 text-center", children: [
407
422
  /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("posts.list.empty") }),
408
423
  /* @__PURE__ */ jsx4(Button2, { asChild: true, className: "mt-4", children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.createFirst") }) })
409
- ] }) : /* @__PURE__ */ jsx4("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
424
+ ] }) : /* @__PURE__ */ jsx4("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
410
425
  /* @__PURE__ */ jsx4(TableHeader, { children: /* @__PURE__ */ jsxs2(TableRow, { children: [
411
426
  /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnTitle") }),
412
427
  /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnStatus") }),
@@ -1169,9 +1184,16 @@ function PostForm({ post }) {
1169
1184
  const [body, setBody] = useState5(post?.body ?? EMPTY_TIPTAP_DOC);
1170
1185
  const [status, setStatus] = useState5(post?.status ?? "draft");
1171
1186
  const [tagsInput, setTagsInput] = useState5((post?.tags ?? []).join(", "));
1187
+ const [noLayout, setNoLayout] = useState5(post?.metadata?.no_layout === true);
1172
1188
  const [saving, setSaving] = useState5(false);
1173
1189
  const [error, setError] = useState5(null);
1174
1190
  const [view, setView] = useState5("edit");
1191
+ function buildMetadata() {
1192
+ const next = { ...post?.metadata ?? {} };
1193
+ if (noLayout && format === "html") next.no_layout = true;
1194
+ else delete next.no_layout;
1195
+ return Object.keys(next).length > 0 ? next : void 0;
1196
+ }
1175
1197
  function parseTags(raw) {
1176
1198
  return Array.from(
1177
1199
  new Set(
@@ -1228,6 +1250,7 @@ function PostForm({ post }) {
1228
1250
  }
1229
1251
  setFormat(next);
1230
1252
  setBody(nextBody);
1253
+ if (next !== "html") setNoLayout(false);
1231
1254
  }
1232
1255
  async function save(e) {
1233
1256
  e.preventDefault();
@@ -1235,6 +1258,7 @@ function PostForm({ post }) {
1235
1258
  setError(null);
1236
1259
  try {
1237
1260
  const tags = parseTags(tagsInput);
1261
+ const metadata = buildMetadata();
1238
1262
  if (isEdit) {
1239
1263
  await updatePost(
1240
1264
  post.postId,
@@ -1246,7 +1270,8 @@ function PostForm({ post }) {
1246
1270
  body,
1247
1271
  status,
1248
1272
  publishedAt: status === "published" ? post?.publishedAt ?? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1249
- tags
1273
+ tags,
1274
+ metadata
1250
1275
  },
1251
1276
  { siteId: post.siteId }
1252
1277
  );
@@ -1260,7 +1285,8 @@ function PostForm({ post }) {
1260
1285
  body,
1261
1286
  status,
1262
1287
  publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1263
- tags
1288
+ tags,
1289
+ metadata
1264
1290
  });
1265
1291
  }
1266
1292
  router.push("/admin/posts");
@@ -1465,6 +1491,21 @@ function PostForm({ post }) {
1465
1491
  }
1466
1492
  )
1467
1493
  ] }),
1494
+ format === "html" && /* @__PURE__ */ jsx10("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs8("label", { className: "flex items-start gap-2 text-sm", children: [
1495
+ /* @__PURE__ */ jsx10(
1496
+ "input",
1497
+ {
1498
+ type: "checkbox",
1499
+ checked: noLayout,
1500
+ onChange: (e) => setNoLayout(e.target.checked),
1501
+ className: "mt-1"
1502
+ }
1503
+ ),
1504
+ /* @__PURE__ */ jsxs8("span", { children: [
1505
+ /* @__PURE__ */ jsx10("span", { className: "font-medium", children: t("posts.form.noLayout") }),
1506
+ /* @__PURE__ */ jsx10("span", { className: "block text-xs text-muted-foreground", children: t("posts.form.noLayoutHint") })
1507
+ ] })
1508
+ ] }) }),
1468
1509
  error && /* @__PURE__ */ jsx10("p", { className: "text-sm text-destructive", children: error }),
1469
1510
  /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1470
1511
  /* @__PURE__ */ jsx10(Button7, { type: "submit", disabled: saving, children: saving ? t("common.saving") : isEdit ? t("posts.form.saveChanges") : t("posts.form.createPost") }),
@@ -1478,8 +1519,8 @@ function PostForm({ post }) {
1478
1519
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
1479
1520
  function NewPostPage() {
1480
1521
  const t = useT();
1481
- return /* @__PURE__ */ jsxs9("div", { className: "p-8", children: [
1482
- /* @__PURE__ */ jsx11("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.newTitle") }),
1522
+ return /* @__PURE__ */ jsxs9("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
1523
+ /* @__PURE__ */ jsx11("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.newTitle") }),
1483
1524
  /* @__PURE__ */ jsx11(PostForm, {})
1484
1525
  ] });
1485
1526
  }
@@ -1502,10 +1543,11 @@ function EditPostPage({ params }) {
1502
1543
  else setPost(p);
1503
1544
  }).finally(() => setLoading(false));
1504
1545
  }, [postId]);
1505
- if (loading) return /* @__PURE__ */ jsx12("div", { className: "p-8", children: t("common.loading") });
1546
+ if (loading)
1547
+ return /* @__PURE__ */ jsx12("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: t("common.loading") });
1506
1548
  if (missing) notFound();
1507
- return /* @__PURE__ */ jsxs10("div", { className: "p-8", children: [
1508
- /* @__PURE__ */ jsx12("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.editTitle") }),
1549
+ return /* @__PURE__ */ jsxs10("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
1550
+ /* @__PURE__ */ jsx12("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.editTitle") }),
1509
1551
  post && /* @__PURE__ */ jsx12(PostForm, { post })
1510
1552
  ] });
1511
1553
  }
@@ -1749,8 +1791,8 @@ function MediaUploader() {
1749
1791
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1750
1792
  function MediaPage() {
1751
1793
  const t = useT();
1752
- return /* @__PURE__ */ jsxs12("div", { className: "p-8", children: [
1753
- /* @__PURE__ */ jsx14("h1", { className: "mb-8 text-3xl font-bold", children: t("media.title") }),
1794
+ return /* @__PURE__ */ jsxs12("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
1795
+ /* @__PURE__ */ jsx14("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("media.title") }),
1754
1796
  /* @__PURE__ */ jsx14(MediaUploader, {})
1755
1797
  ] });
1756
1798
  }
@@ -1946,77 +1988,150 @@ function LoginPage() {
1946
1988
  }
1947
1989
 
1948
1990
  // src/components/sidebar.tsx
1991
+ import { useEffect as useEffect7, useState as useState9 } from "react";
1949
1992
  import Link4 from "next/link";
1950
1993
  import { usePathname } from "next/navigation";
1951
1994
  import { signOut } from "aws-amplify/auth";
1952
- import { LayoutDashboard, FileText as FileText2, Image as Image2, Globe, LogOut, ExternalLink } from "lucide-react";
1995
+ import {
1996
+ LayoutDashboard,
1997
+ FileText as FileText2,
1998
+ Image as Image2,
1999
+ Globe,
2000
+ Users,
2001
+ LogOut,
2002
+ ExternalLink,
2003
+ Menu,
2004
+ X
2005
+ } from "lucide-react";
1953
2006
  import { Button as Button10, cn as cn3 } from "@ampless/runtime/ui";
1954
- import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2007
+ import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1955
2008
  var navItems = [
1956
2009
  { href: "/admin", key: "sidebar.dashboard", icon: LayoutDashboard },
1957
2010
  { href: "/admin/posts", key: "sidebar.posts", icon: FileText2 },
1958
2011
  { href: "/admin/media", key: "sidebar.media", icon: Image2 },
1959
- { href: "/admin/sites", key: "sidebar.sites", icon: Globe }
2012
+ { href: "/admin/sites", key: "sidebar.sites", icon: Globe },
2013
+ { href: "/admin/users", key: "sidebar.users", icon: Users, adminOnly: true }
1960
2014
  ];
1961
2015
  function Sidebar({
1962
2016
  email,
1963
- siteSelector
2017
+ siteSelector,
2018
+ isAdmin
1964
2019
  }) {
1965
2020
  const pathname = usePathname();
1966
2021
  const t = useT();
1967
- return /* @__PURE__ */ jsxs14("aside", { className: "flex h-screen w-60 flex-col border-r bg-muted/30", children: [
1968
- /* @__PURE__ */ jsx16("div", { className: "border-b p-4", children: /* @__PURE__ */ jsx16(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }) }),
1969
- siteSelector ? /* @__PURE__ */ jsx16("div", { className: "border-b", children: siteSelector }) : null,
1970
- /* @__PURE__ */ jsx16("nav", { className: "flex-1 space-y-1 p-2", children: navItems.map((item) => {
1971
- const Icon = item.icon;
1972
- const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
1973
- return /* @__PURE__ */ jsxs14(
1974
- Link4,
1975
- {
1976
- href: item.href,
1977
- className: cn3(
1978
- "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
1979
- isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1980
- ),
1981
- children: [
1982
- /* @__PURE__ */ jsx16(Icon, { className: "h-4 w-4" }),
1983
- t(item.key)
1984
- ]
1985
- },
1986
- item.href
1987
- );
1988
- }) }),
1989
- /* @__PURE__ */ jsxs14("div", { className: "border-t p-2 space-y-1", children: [
1990
- /* @__PURE__ */ jsxs14(
1991
- Link4,
1992
- {
1993
- href: "/",
1994
- target: "_blank",
1995
- className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
1996
- children: [
1997
- /* @__PURE__ */ jsx16(ExternalLink, { className: "h-4 w-4" }),
1998
- t("sidebar.viewSite")
1999
- ]
2000
- }
2001
- ),
2002
- /* @__PURE__ */ jsx16("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
2003
- /* @__PURE__ */ jsxs14(
2022
+ const [open, setOpen] = useState9(false);
2023
+ useEffect7(() => {
2024
+ setOpen(false);
2025
+ }, [pathname]);
2026
+ useEffect7(() => {
2027
+ if (!open) return;
2028
+ const prev = document.body.style.overflow;
2029
+ document.body.style.overflow = "hidden";
2030
+ return () => {
2031
+ document.body.style.overflow = prev;
2032
+ };
2033
+ }, [open]);
2034
+ return /* @__PURE__ */ jsxs14(Fragment5, { children: [
2035
+ /* @__PURE__ */ jsxs14("header", { className: "sticky top-0 z-30 flex h-14 items-center justify-between border-b bg-background px-4 md:hidden", children: [
2036
+ /* @__PURE__ */ jsx16(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }),
2037
+ /* @__PURE__ */ jsx16(
2004
2038
  Button10,
2005
2039
  {
2006
2040
  variant: "ghost",
2007
- size: "sm",
2008
- className: "w-full justify-start gap-3",
2009
- onClick: async () => {
2010
- await signOut();
2011
- window.location.href = "/login";
2012
- },
2013
- children: [
2014
- /* @__PURE__ */ jsx16(LogOut, { className: "h-4 w-4" }),
2015
- t("sidebar.signOut")
2016
- ]
2041
+ size: "icon",
2042
+ "aria-label": t("sidebar.openMenu"),
2043
+ "aria-expanded": open,
2044
+ onClick: () => setOpen(true),
2045
+ children: /* @__PURE__ */ jsx16(Menu, { className: "h-5 w-5" })
2017
2046
  }
2018
2047
  )
2019
- ] })
2048
+ ] }),
2049
+ open && /* @__PURE__ */ jsx16(
2050
+ "div",
2051
+ {
2052
+ className: "fixed inset-0 z-40 bg-black/40 md:hidden",
2053
+ "aria-hidden": "true",
2054
+ onClick: () => setOpen(false)
2055
+ }
2056
+ ),
2057
+ /* @__PURE__ */ jsxs14(
2058
+ "aside",
2059
+ {
2060
+ className: cn3(
2061
+ "fixed inset-y-0 left-0 z-50 flex w-60 flex-col border-r bg-muted/30 transition-transform md:sticky md:top-0 md:h-screen md:translate-x-0",
2062
+ open ? "translate-x-0" : "-translate-x-full md:translate-x-0"
2063
+ ),
2064
+ "aria-label": t("sidebar.brand"),
2065
+ children: [
2066
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between border-b p-4", children: [
2067
+ /* @__PURE__ */ jsx16(Link4, { href: "/admin", className: "font-semibold", children: t("sidebar.brand") }),
2068
+ /* @__PURE__ */ jsx16(
2069
+ Button10,
2070
+ {
2071
+ variant: "ghost",
2072
+ size: "icon",
2073
+ className: "md:hidden",
2074
+ "aria-label": t("sidebar.closeMenu"),
2075
+ onClick: () => setOpen(false),
2076
+ children: /* @__PURE__ */ jsx16(X, { className: "h-5 w-5" })
2077
+ }
2078
+ )
2079
+ ] }),
2080
+ siteSelector ? /* @__PURE__ */ jsx16("div", { className: "border-b", children: siteSelector }) : null,
2081
+ /* @__PURE__ */ jsx16("nav", { className: "flex-1 space-y-1 overflow-y-auto p-2", children: navItems.map((item) => {
2082
+ if (item.adminOnly && !isAdmin) return null;
2083
+ const Icon = item.icon;
2084
+ const isActive = pathname === item.href || item.href !== "/admin" && pathname?.startsWith(item.href);
2085
+ return /* @__PURE__ */ jsxs14(
2086
+ Link4,
2087
+ {
2088
+ href: item.href,
2089
+ className: cn3(
2090
+ "flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors",
2091
+ isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
2092
+ ),
2093
+ children: [
2094
+ /* @__PURE__ */ jsx16(Icon, { className: "h-4 w-4" }),
2095
+ t(item.key)
2096
+ ]
2097
+ },
2098
+ item.href
2099
+ );
2100
+ }) }),
2101
+ /* @__PURE__ */ jsxs14("div", { className: "border-t p-2 space-y-1", children: [
2102
+ /* @__PURE__ */ jsxs14(
2103
+ Link4,
2104
+ {
2105
+ href: "/",
2106
+ target: "_blank",
2107
+ className: "flex items-center gap-3 rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground",
2108
+ children: [
2109
+ /* @__PURE__ */ jsx16(ExternalLink, { className: "h-4 w-4" }),
2110
+ t("sidebar.viewSite")
2111
+ ]
2112
+ }
2113
+ ),
2114
+ /* @__PURE__ */ jsx16("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
2115
+ /* @__PURE__ */ jsxs14(
2116
+ Button10,
2117
+ {
2118
+ variant: "ghost",
2119
+ size: "sm",
2120
+ className: "w-full justify-start gap-3",
2121
+ onClick: async () => {
2122
+ await signOut();
2123
+ window.location.href = "/login";
2124
+ },
2125
+ children: [
2126
+ /* @__PURE__ */ jsx16(LogOut, { className: "h-4 w-4" }),
2127
+ t("sidebar.signOut")
2128
+ ]
2129
+ }
2130
+ )
2131
+ ] })
2132
+ ]
2133
+ }
2134
+ )
2020
2135
  ] });
2021
2136
  }
2022
2137
 
@@ -2046,7 +2161,7 @@ function SiteSelector({ current, sites }) {
2046
2161
  }
2047
2162
 
2048
2163
  // src/components/site-settings-form.tsx
2049
- import { useState as useState9 } from "react";
2164
+ import { useState as useState10 } from "react";
2050
2165
  import { useRouter as useRouter4 } from "next/navigation";
2051
2166
  import { setSiteSetting } from "ampless";
2052
2167
  import { Button as Button11, Input as Input5, Label as Label4, Textarea as Textarea2 } from "@ampless/runtime/ui";
@@ -2063,10 +2178,10 @@ var KEYS = [
2063
2178
  function SiteSettingsForm({ siteId, initial, fallback }) {
2064
2179
  const router = useRouter4();
2065
2180
  const t = useT();
2066
- const [values, setValues] = useState9(initial);
2067
- const [saving, setSaving] = useState9(false);
2068
- const [error, setError] = useState9(null);
2069
- const [info, setInfo] = useState9(null);
2181
+ const [values, setValues] = useState10(initial);
2182
+ const [saving, setSaving] = useState10(false);
2183
+ const [error, setError] = useState10(null);
2184
+ const [info, setInfo] = useState10(null);
2070
2185
  function update(key, value) {
2071
2186
  setValues((prev) => ({ ...prev, [key]: value }));
2072
2187
  }
@@ -2208,7 +2323,7 @@ function SiteSettingsForm({ siteId, initial, fallback }) {
2208
2323
  }
2209
2324
 
2210
2325
  // src/components/theme-settings-form.tsx
2211
- import { useState as useState10 } from "react";
2326
+ import { useState as useState11 } from "react";
2212
2327
  import { useRouter as useRouter5 } from "next/navigation";
2213
2328
  import {
2214
2329
  setSiteSetting as setSiteSetting2,
@@ -2232,14 +2347,14 @@ function ThemeSettingsForm({
2232
2347
  const router = useRouter5();
2233
2348
  const t = useT();
2234
2349
  const locale = useLocale();
2235
- const [state, setState] = useState10({ values: initial, touched: {} });
2236
- const [pendingTheme, setPendingTheme] = useState10(activeTheme);
2237
- const [optimisticActive, setOptimisticActive] = useState10(activeTheme);
2238
- const [saving, setSaving] = useState10(false);
2239
- const [switching, setSwitching] = useState10(false);
2240
- const [error, setError] = useState10(null);
2241
- const [info, setInfo] = useState10(null);
2242
- const [invalid, setInvalid] = useState10({});
2350
+ const [state, setState] = useState11({ values: initial, touched: {} });
2351
+ const [pendingTheme, setPendingTheme] = useState11(activeTheme);
2352
+ const [optimisticActive, setOptimisticActive] = useState11(activeTheme);
2353
+ const [saving, setSaving] = useState11(false);
2354
+ const [switching, setSwitching] = useState11(false);
2355
+ const [error, setError] = useState11(null);
2356
+ const [info, setInfo] = useState11(null);
2357
+ const [invalid, setInvalid] = useState11({});
2243
2358
  function update(key, value) {
2244
2359
  setState((prev) => ({
2245
2360
  values: { ...prev.values, [key]: value },
@@ -34,9 +34,12 @@ var en_default = {
34
34
  posts: "Posts",
35
35
  media: "Media",
36
36
  sites: "Sites",
37
+ users: "Users",
37
38
  viewSite: "View site",
38
39
  signOut: "Sign out",
39
- site: "Site"
40
+ site: "Site",
41
+ openMenu: "Open menu",
42
+ closeMenu: "Close menu"
40
43
  },
41
44
  dashboard: {
42
45
  title: "Dashboard",
@@ -76,6 +79,8 @@ var en_default = {
76
79
  tagsPlaceholder: "comma, separated, tags",
77
80
  tagsHint: "Used to group posts on tag pages (e.g. /tag/tech).",
78
81
  status: "Status",
82
+ noLayout: "No layout",
83
+ noLayoutHint: "Serve this post as bare HTML \u2014 no theme header, footer, or root layout. The body is returned as the entire response. Best paired with format: HTML when the body contains a full <!DOCTYPE html>\u2026</html> document.",
79
84
  saveChanges: "Save changes",
80
85
  createPost: "Create post",
81
86
  delete: "Delete",
@@ -147,6 +152,25 @@ var en_default = {
147
152
  label: "Site"
148
153
  }
149
154
  },
155
+ users: {
156
+ list: {
157
+ title: "Users",
158
+ description: "Promote signed-up users to admin or editor. Sign-ups always start with no role and must be granted access here.",
159
+ columnEmail: "Email",
160
+ columnRole: "Role",
161
+ columnActions: "Actions",
162
+ save: "Save",
163
+ saving: "Saving...",
164
+ saved: "Saved.",
165
+ cannotEditSelf: "You cannot change your own role.",
166
+ empty: "No users yet.",
167
+ loading: "Loading users...",
168
+ error: "Failed to load users",
169
+ roleAdmin: "Admin",
170
+ roleEditor: "Editor",
171
+ roleNone: "None"
172
+ }
173
+ },
150
174
  theme: {
151
175
  title: "Theme",
152
176
  activeLabel: "Active theme",
@@ -269,9 +293,12 @@ var ja_default = {
269
293
  posts: "\u8A18\u4E8B",
270
294
  media: "\u30E1\u30C7\u30A3\u30A2",
271
295
  sites: "\u30B5\u30A4\u30C8",
296
+ users: "\u30E6\u30FC\u30B6\u30FC",
272
297
  viewSite: "\u30B5\u30A4\u30C8\u3092\u8868\u793A",
273
298
  signOut: "\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8",
274
- site: "\u30B5\u30A4\u30C8"
299
+ site: "\u30B5\u30A4\u30C8",
300
+ openMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u958B\u304F",
301
+ closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B"
275
302
  },
276
303
  dashboard: {
277
304
  title: "\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9",
@@ -311,6 +338,8 @@ var ja_default = {
311
338
  tagsPlaceholder: "\u30AB\u30F3\u30DE, \u533A\u5207\u308A, \u30BF\u30B0",
312
339
  tagsHint: "\u30BF\u30B0\u30DA\u30FC\u30B8\u3067\u8A18\u4E8B\u3092\u30B0\u30EB\u30FC\u30D4\u30F3\u30B0 (\u4F8B: /tag/tech)\u3002",
313
340
  status: "\u30B9\u30C6\u30FC\u30BF\u30B9",
341
+ noLayout: "\u30EC\u30A4\u30A2\u30A6\u30C8\u3092\u4F7F\u308F\u306A\u3044",
342
+ noLayoutHint: "\u30C6\u30FC\u30DE\u306E\u30D8\u30C3\u30C0\u30FC / \u30D5\u30C3\u30BF\u30FC / root layout \u3092\u4ECB\u3055\u305A\u3001\u672C\u6587\u3092 HTTP \u30EC\u30B9\u30DD\u30F3\u30B9\u672C\u4F53\u3068\u3057\u3066\u305D\u306E\u307E\u307E\u8FD4\u3057\u307E\u3059\u3002\u672C\u6587\u306B <!DOCTYPE html>\u2026</html> \u304C\u542B\u307E\u308C\u308B\u5834\u5408\u306F\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8: HTML \u3068\u306E\u7D44\u307F\u5408\u308F\u305B\u304C\u6700\u9069\u3067\u3059\u3002",
314
343
  saveChanges: "\u5909\u66F4\u3092\u4FDD\u5B58",
315
344
  createPost: "\u8A18\u4E8B\u3092\u4F5C\u6210",
316
345
  delete: "\u524A\u9664",
@@ -382,6 +411,25 @@ var ja_default = {
382
411
  label: "\u30B5\u30A4\u30C8"
383
412
  }
384
413
  },
414
+ users: {
415
+ list: {
416
+ title: "\u30E6\u30FC\u30B6\u30FC",
417
+ description: "\u30B5\u30A4\u30F3\u30A2\u30C3\u30D7\u6E08\u307F\u30E6\u30FC\u30B6\u30FC\u3092\u7BA1\u7406\u8005\u307E\u305F\u306F\u7DE8\u96C6\u8005\u306B\u6607\u683C\u3057\u307E\u3059\u3002\u30B5\u30A4\u30F3\u30A2\u30C3\u30D7\u76F4\u5F8C\u306F\u30ED\u30FC\u30EB\u7121\u3057\u306E\u305F\u3081\u3001\u3053\u3053\u3067\u30A2\u30AF\u30BB\u30B9\u3092\u4ED8\u4E0E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
418
+ columnEmail: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9",
419
+ columnRole: "\u30ED\u30FC\u30EB",
420
+ columnActions: "\u64CD\u4F5C",
421
+ save: "\u4FDD\u5B58",
422
+ saving: "\u4FDD\u5B58\u4E2D...",
423
+ saved: "\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
424
+ cannotEditSelf: "\u81EA\u5206\u81EA\u8EAB\u306E\u30ED\u30FC\u30EB\u306F\u5909\u66F4\u3067\u304D\u307E\u305B\u3093\u3002",
425
+ empty: "\u30E6\u30FC\u30B6\u30FC\u304C\u3044\u307E\u305B\u3093\u3002",
426
+ loading: "\u30E6\u30FC\u30B6\u30FC\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
427
+ error: "\u30E6\u30FC\u30B6\u30FC\u4E00\u89A7\u306E\u53D6\u5F97\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
428
+ roleAdmin: "\u7BA1\u7406\u8005",
429
+ roleEditor: "\u7DE8\u96C6\u8005",
430
+ roleNone: "\u306A\u3057"
431
+ }
432
+ },
385
433
  theme: {
386
434
  title: "\u30C6\u30FC\u30DE",
387
435
  activeLabel: "\u30A2\u30AF\u30C6\u30A3\u30D6\u30C6\u30FC\u30DE",
@@ -1,5 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { L as Locale, D as Dictionary } from '../i18n-ByHM_Bho.js';
2
+ import { L as Locale, D as Dictionary } from '../i18n-Bc4SYgWx.js';
3
3
  import { Config, Post, ThemeManifest, LocalizedString, MediaProcessingDefaults } from 'ampless';
4
4
  import { AmplessOutputs } from '@ampless/runtime';
5
5
  export { A as AdminDashboard, E as EditPostPage, L as LoginPage, M as MediaPage, N as NewPostPage, P as PostsList } from '../login-view-BKrSZLJu.js';
@@ -108,10 +108,12 @@ declare function uploadProcessedImage(file: File, options: ProcessOptions): Prom
108
108
  url: string;
109
109
  }>;
110
110
 
111
- declare function Sidebar({ email, siteSelector, }: {
111
+ declare function Sidebar({ email, siteSelector, isAdmin, }: {
112
112
  email: string;
113
113
  /** Rendered above the main nav in multi-site mode. */
114
114
  siteSelector?: React.ReactNode;
115
+ /** Gates `adminOnly` nav entries (user management). */
116
+ isAdmin: boolean;
115
117
  }): react_jsx_runtime.JSX.Element;
116
118
 
117
119
  interface SiteOption {
@@ -22,12 +22,12 @@ import {
22
22
  uploadProcessedImage,
23
23
  useLocale,
24
24
  useT
25
- } from "../chunk-7WFZULH7.js";
25
+ } from "../chunk-GXPSAOES.js";
26
26
  import {
27
27
  ADMIN_SITE_COOKIE,
28
28
  publicMediaUrl,
29
29
  setAdminMediaContext
30
- } from "../chunk-FI7CM4LH.js";
30
+ } from "../chunk-L5NHN3MY.js";
31
31
  import {
32
32
  invalidateSiteSettingsCache
33
33
  } from "../chunk-VXEVLHGL.js";