@ampless/admin 0.2.0-alpha.0 → 0.2.0-alpha.10

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.
@@ -1,296 +1,37 @@
1
1
  import {
2
- I18nProvider,
3
- MediaUploader,
4
- PostForm,
2
+ AdminDashboard,
3
+ AdminProviders,
4
+ EditPostPage,
5
+ LoginPage,
6
+ MediaPage,
7
+ NewPostPage,
8
+ PostsList,
5
9
  Sidebar,
6
10
  SiteSelector,
7
11
  SiteSettingsForm,
8
- ThemeSettingsForm,
9
- setAdminCmsConfigClient,
10
- useT
11
- } from "../chunk-BN6BW7MP.js";
12
+ ThemeSettingsForm
13
+ } from "../chunk-PAL62MXF.js";
14
+ import "../chunk-QMVFRT62.js";
12
15
  import {
13
- readAdminSiteIdFromCookie,
14
- setAdminCmsConfig,
15
- setAdminMediaContext
16
- } from "../chunk-TJR3ALRJ.js";
16
+ UsersListView
17
+ } from "../chunk-UOU7KQLR.js";
18
+ import {
19
+ I18nProvider
20
+ } from "../chunk-OFHKZNZS.js";
21
+ import "../chunk-OPQ3SAZJ.js";
22
+ import "../chunk-VXEVLHGL.js";
17
23
 
18
24
  // src/pages/admin-layout.tsx
19
25
  import { redirect } from "next/navigation";
20
-
21
- // src/pages/admin-providers.tsx
22
- import { useEffect } from "react";
23
-
24
- // src/lib/amplify-client.ts
25
- import { Amplify } from "aws-amplify";
26
- var configured = false;
27
- function configureAmplify(outputs) {
28
- if (configured) return;
29
- Amplify.configure(outputs, { ssr: true });
30
- configured = true;
31
- }
32
-
33
- // src/lib/posts-provider.ts
34
- import { generateClient } from "aws-amplify/api";
35
- import {
36
- setPostsProvider,
37
- composeSiteIdStatus,
38
- composeSiteIdSlug
39
- } from "ampless";
40
- function encodeBody(value) {
41
- if (typeof value === "string") return value;
42
- return JSON.stringify(value ?? null);
43
- }
44
- function decodeBody(value) {
45
- if (typeof value !== "string") return value;
46
- try {
47
- return JSON.parse(value);
48
- } catch {
49
- return value;
50
- }
51
- }
52
- function toCorePost(p) {
26
+ import { jsx, jsxs } from "react/jsx-runtime";
27
+ function sanitizeCmsConfigForClient(config) {
53
28
  return {
54
- postId: p.postId,
55
- siteId: p.siteId,
56
- slug: p.slug,
57
- title: p.title,
58
- excerpt: p.excerpt ?? void 0,
59
- format: p.format ?? "markdown",
60
- body: decodeBody(p.body),
61
- status: p.status ?? "draft",
62
- publishedAt: p.publishedAt ?? void 0,
63
- tags: (p.tags ?? []).filter((t) => typeof t === "string")
64
- };
65
- }
66
- function postTagEntries(post) {
67
- if (post.status !== "published" || !post.publishedAt || !post.tags?.length) return [];
68
- return post.tags.map((tag) => ({
69
- siteIdTag: `${post.siteId}#${tag}`,
70
- publishedAtPostId: `${post.publishedAt}#${post.postId}`
71
- }));
72
- }
73
- function entryKey(e) {
74
- return `${e.siteIdTag}|${e.publishedAtPostId}`;
75
- }
76
- var installed = false;
77
- function installAdminPostsProvider() {
78
- if (installed) return;
79
- installed = true;
80
- const client = generateClient();
81
- async function syncPostTags(post, oldPost) {
82
- const oldEntries = oldPost ? postTagEntries(oldPost) : [];
83
- const newEntries = postTagEntries(post);
84
- const oldKeys = new Set(oldEntries.map(entryKey));
85
- const newKeys = new Set(newEntries.map(entryKey));
86
- await Promise.all(
87
- oldEntries.filter((e) => !newKeys.has(entryKey(e))).map((e) => client.models.PostTag.delete(e))
88
- );
89
- await Promise.all(
90
- newEntries.filter((e) => !oldKeys.has(entryKey(e))).map(
91
- (e) => client.models.PostTag.create({
92
- siteIdTag: e.siteIdTag,
93
- publishedAtPostId: e.publishedAtPostId,
94
- siteId: post.siteId,
95
- tag: e.siteIdTag.slice(post.siteId.length + 1),
96
- postId: post.postId,
97
- publishedAt: post.publishedAt,
98
- slug: post.slug,
99
- title: post.title,
100
- excerpt: post.excerpt,
101
- tags: post.tags ?? []
102
- })
103
- )
104
- );
105
- await Promise.all(
106
- newEntries.filter((e) => oldKeys.has(entryKey(e))).map(
107
- (e) => client.models.PostTag.update({
108
- siteIdTag: e.siteIdTag,
109
- publishedAtPostId: e.publishedAtPostId,
110
- slug: post.slug,
111
- title: post.title,
112
- excerpt: post.excerpt,
113
- tags: post.tags ?? []
114
- })
115
- )
116
- );
117
- }
118
- const provider = {
119
- async list(opts = {}) {
120
- const siteId = opts.siteId ?? "default";
121
- const status = opts.status ?? "published";
122
- const filter = { siteId: { eq: siteId } };
123
- if (status !== "all") filter.status = { eq: status };
124
- const { data } = await client.models.Post.list({ filter, limit: opts.limit ?? 100 });
125
- return data.map(toCorePost);
126
- },
127
- async get(slug, opts = {}) {
128
- const siteId = opts.siteId ?? "default";
129
- const { data } = await client.models.Post.list({
130
- filter: { siteId: { eq: siteId }, slug: { eq: slug } },
131
- limit: 1
132
- });
133
- return data[0] ? toCorePost(data[0]) : null;
134
- },
135
- async getById(postId, opts = {}) {
136
- const siteId = opts.siteId ?? "default";
137
- const { data } = await client.models.Post.get({ siteId, postId });
138
- return data ? toCorePost(data) : null;
139
- },
140
- async create(input) {
141
- const postId = input.postId ?? `post-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
142
- const { data, errors } = await client.models.Post.create({
143
- siteId: input.siteId,
144
- postId,
145
- slug: input.slug,
146
- title: input.title,
147
- excerpt: input.excerpt,
148
- format: input.format,
149
- body: encodeBody(input.body),
150
- status: input.status,
151
- publishedAt: input.publishedAt,
152
- tags: input.tags,
153
- // Denormalized GSI keys. Must match every change to slug /
154
- // status — see the update() branch below.
155
- siteIdStatus: composeSiteIdStatus(input.siteId, input.status),
156
- siteIdSlug: composeSiteIdSlug(input.siteId, input.slug)
157
- });
158
- if (errors || !data) throw new Error(errors?.[0]?.message ?? "Failed to create post");
159
- const created = toCorePost(data);
160
- await syncPostTags(created, null);
161
- return created;
162
- },
163
- async update(postId, patch, opts = {}) {
164
- const siteId = opts.siteId ?? "default";
165
- const oldPost = await this.getById(postId, { siteId });
166
- const nextStatus = patch.status ?? oldPost?.status;
167
- const nextSlug = patch.slug ?? oldPost?.slug;
168
- const { data, errors } = await client.models.Post.update({
169
- siteId,
170
- postId,
171
- ...patch.slug !== void 0 && { slug: patch.slug },
172
- ...patch.title !== void 0 && { title: patch.title },
173
- ...patch.excerpt !== void 0 && { excerpt: patch.excerpt },
174
- ...patch.format !== void 0 && { format: patch.format },
175
- ...patch.body !== void 0 && { body: encodeBody(patch.body) },
176
- ...patch.status !== void 0 && { status: patch.status },
177
- ...patch.publishedAt !== void 0 && { publishedAt: patch.publishedAt },
178
- ...patch.tags !== void 0 && { tags: patch.tags },
179
- ...patch.status !== void 0 && nextStatus && { siteIdStatus: composeSiteIdStatus(siteId, nextStatus) },
180
- ...patch.slug !== void 0 && nextSlug && { siteIdSlug: composeSiteIdSlug(siteId, nextSlug) }
181
- });
182
- if (errors || !data) throw new Error(errors?.[0]?.message ?? "Failed to update post");
183
- const updated = toCorePost(data);
184
- await syncPostTags(updated, oldPost);
185
- return updated;
186
- },
187
- async remove(postId, opts = {}) {
188
- const siteId = opts.siteId ?? "default";
189
- const oldPost = await this.getById(postId, { siteId });
190
- if (oldPost) {
191
- await syncPostTags({ ...oldPost, status: "draft" }, oldPost);
192
- }
193
- const { errors } = await client.models.Post.delete({ siteId, postId });
194
- if (errors) throw new Error(errors[0]?.message ?? "Failed to delete post");
195
- }
196
- };
197
- setPostsProvider(provider);
198
- }
199
-
200
- // src/lib/kv-provider.ts
201
- import { generateClient as generateClient2 } from "aws-amplify/api";
202
- import { setKvStore } from "ampless";
203
- var installed2 = false;
204
- function installAdminKvProvider() {
205
- if (installed2) return;
206
- installed2 = true;
207
- const client = generateClient2();
208
- function requireModel() {
209
- const m = client.models.KvStore;
210
- if (!m) {
211
- throw new Error(
212
- "KvStore model is not available on the AppSync client. Did you redeploy the sandbox? Run `npx ampx sandbox` and wait for it to finish, then reload this page."
213
- );
214
- }
215
- return m;
216
- }
217
- function encodeValue(value) {
218
- return JSON.stringify(value ?? null);
219
- }
220
- function decodeValue(raw) {
221
- if (typeof raw !== "string") return raw;
222
- try {
223
- return JSON.parse(raw);
224
- } catch {
225
- return raw;
226
- }
227
- }
228
- const store = {
229
- async get(pk, sk) {
230
- const model = requireModel();
231
- const { data } = await model.get({ pk, sk });
232
- return data ? decodeValue(data.value) : null;
233
- },
234
- async query(pk) {
235
- const model = requireModel();
236
- const { data } = await model.list({
237
- filter: { pk: { eq: pk } },
238
- limit: 1e3
239
- });
240
- return (data ?? []).map((row) => ({
241
- pk: row.pk,
242
- sk: row.sk,
243
- value: decodeValue(row.value),
244
- ttl: row.ttl ?? void 0
245
- }));
246
- },
247
- async put(pk, sk, value, opts) {
248
- const model = requireModel();
249
- const ttl = opts?.ttlSeconds ? Math.floor(Date.now() / 1e3) + opts.ttlSeconds : void 0;
250
- const existing = await model.get({ pk, sk });
251
- if (existing.data) {
252
- const { errors } = await model.update({
253
- pk,
254
- sk,
255
- value: encodeValue(value),
256
- ttl: ttl ?? null
257
- });
258
- if (errors) throw new Error(errors[0]?.message ?? "KvStore.update failed");
259
- } else {
260
- const { errors } = await model.create({
261
- pk,
262
- sk,
263
- value: encodeValue(value),
264
- ttl
265
- });
266
- if (errors) throw new Error(errors[0]?.message ?? "KvStore.create failed");
267
- }
268
- },
269
- async remove(pk, sk) {
270
- const model = requireModel();
271
- const { errors } = await model.delete({ pk, sk });
272
- if (errors) throw new Error(errors[0]?.message ?? "KvStore.delete failed");
273
- }
29
+ ...config,
30
+ plugins: (config.plugins ?? []).map(
31
+ (p) => typeof p === "string" ? p : { name: p.name, apiVersion: p.apiVersion, trust_level: p.trust_level }
32
+ )
274
33
  };
275
- setKvStore(store);
276
- }
277
-
278
- // src/pages/admin-providers.tsx
279
- import { Fragment, jsx } from "react/jsx-runtime";
280
- function AdminProviders({ outputs, cmsConfig, children }) {
281
- configureAmplify(outputs);
282
- setAdminCmsConfig(cmsConfig);
283
- setAdminCmsConfigClient(cmsConfig);
284
- setAdminMediaContext(outputs, cmsConfig);
285
- useEffect(() => {
286
- installAdminPostsProvider();
287
- installAdminKvProvider();
288
- }, []);
289
- return /* @__PURE__ */ jsx(Fragment, { children });
290
34
  }
291
-
292
- // src/pages/admin-layout.tsx
293
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
294
35
  function createAdminLayout(admin) {
295
36
  async function AdminLayout({ children }) {
296
37
  const session = await admin.getServerSession();
@@ -299,216 +40,87 @@ function createAdminLayout(admin) {
299
40
  }
300
41
  const sites = admin.adminSiteOptions();
301
42
  const currentSiteId = await admin.currentAdminSiteId();
302
- const selector = sites.length > 0 ? /* @__PURE__ */ jsx2(SiteSelector, { current: currentSiteId, sites }) : null;
303
- return /* @__PURE__ */ jsx2(AdminProviders, { outputs: admin.outputs, cmsConfig: admin.cmsConfig, children: /* @__PURE__ */ jsx2(I18nProvider, { locale: admin.locale, dict: admin.dict, children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen", children: [
304
- /* @__PURE__ */ jsx2(Sidebar, { email: session.email, siteSelector: selector }),
305
- /* @__PURE__ */ jsx2("main", { className: "flex-1 overflow-auto", children })
43
+ const selector = sites.length > 0 ? /* @__PURE__ */ jsx(SiteSelector, { current: currentSiteId, sites }) : null;
44
+ return /* @__PURE__ */ jsx(AdminProviders, { outputs: admin.outputs, cmsConfig: sanitizeCmsConfigForClient(admin.cmsConfig), children: /* @__PURE__ */ jsx(I18nProvider, { locale: admin.locale, dict: admin.dict, children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen flex-col md:flex-row", children: [
45
+ /* @__PURE__ */ jsx(
46
+ Sidebar,
47
+ {
48
+ email: session.email,
49
+ siteSelector: selector,
50
+ isAdmin: admin.isAdmin(session)
51
+ }
52
+ ),
53
+ /* @__PURE__ */ jsx("main", { className: "min-w-0 flex-1", children })
306
54
  ] }) }) });
307
55
  }
308
56
  return AdminLayout;
309
57
  }
310
58
 
311
59
  // src/pages/dashboard.tsx
312
- import { useEffect as useEffect2, useState } from "react";
313
- import Link from "next/link";
314
- import { listPosts } from "ampless";
315
- import { Button, Card, CardContent, CardHeader, CardTitle } from "@ampless/runtime/ui";
316
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
317
- function AdminDashboard() {
318
- const t = useT();
319
- const [posts, setPosts] = useState([]);
320
- const [loading, setLoading] = useState(true);
321
- useEffect2(() => {
322
- listPosts({ status: "all" }).then(setPosts).finally(() => setLoading(false));
323
- }, []);
324
- const published = posts.filter((p) => p.status === "published").length;
325
- const drafts = posts.filter((p) => p.status === "draft").length;
326
- return /* @__PURE__ */ jsxs2("div", { className: "p-8", children: [
327
- /* @__PURE__ */ jsxs2("div", { className: "mb-8 flex items-center justify-between", children: [
328
- /* @__PURE__ */ jsx3("h1", { className: "text-3xl font-bold", children: t("dashboard.title") }),
329
- /* @__PURE__ */ jsx3(Button, { asChild: true, children: /* @__PURE__ */ jsx3(Link, { href: "/admin/posts/new", children: t("dashboard.newPost") }) })
330
- ] }),
331
- /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [
332
- /* @__PURE__ */ jsxs2(Card, { children: [
333
- /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.totalPosts") }) }),
334
- /* @__PURE__ */ jsxs2(CardContent, { children: [
335
- /* @__PURE__ */ jsx3("p", { className: "text-3xl font-bold", children: loading ? "\u2014" : posts.length }),
336
- /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: t("dashboard.totalLabel") })
337
- ] })
338
- ] }),
339
- /* @__PURE__ */ jsxs2(Card, { children: [
340
- /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.published") }) }),
341
- /* @__PURE__ */ jsx3(CardContent, { children: /* @__PURE__ */ jsx3("p", { className: "text-3xl font-bold", children: loading ? "\u2014" : published }) })
342
- ] }),
343
- /* @__PURE__ */ jsxs2(Card, { children: [
344
- /* @__PURE__ */ jsx3(CardHeader, { children: /* @__PURE__ */ jsx3(CardTitle, { children: t("dashboard.drafts") }) }),
345
- /* @__PURE__ */ jsx3(CardContent, { children: /* @__PURE__ */ jsx3("p", { className: "text-3xl font-bold", children: loading ? "\u2014" : drafts }) })
346
- ] })
347
- ] })
348
- ] });
349
- }
350
60
  function createAdminDashboardPage(_admin) {
351
61
  return AdminDashboard;
352
62
  }
353
63
 
354
64
  // src/pages/posts-list.tsx
355
- import { useEffect as useEffect3, useState as useState2 } from "react";
356
- import Link2 from "next/link";
357
- import { listPosts as listPosts2 } from "ampless";
358
- import {
359
- Button as Button2,
360
- Table,
361
- TableBody,
362
- TableCell,
363
- TableHead,
364
- TableHeader,
365
- TableRow
366
- } from "@ampless/runtime/ui";
367
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
368
- function PostsList() {
369
- const t = useT();
370
- const [posts, setPosts] = useState2([]);
371
- const [loading, setLoading] = useState2(true);
372
- useEffect3(() => {
373
- const siteId = readAdminSiteIdFromCookie();
374
- listPosts2({ status: "all", siteId }).then(setPosts).finally(() => setLoading(false));
375
- }, []);
376
- return /* @__PURE__ */ jsxs3("div", { className: "p-8", children: [
377
- /* @__PURE__ */ jsxs3("div", { className: "mb-8 flex items-center justify-between", children: [
378
- /* @__PURE__ */ jsx4("h1", { className: "text-3xl font-bold", children: t("posts.list.title") }),
379
- /* @__PURE__ */ jsx4(Button2, { asChild: true, children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.newButton") }) })
380
- ] }),
381
- loading ? /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("common.loading") }) : posts.length === 0 ? /* @__PURE__ */ jsxs3("div", { className: "rounded-md border p-12 text-center", children: [
382
- /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: t("posts.list.empty") }),
383
- /* @__PURE__ */ jsx4(Button2, { asChild: true, className: "mt-4", children: /* @__PURE__ */ jsx4(Link2, { href: "/admin/posts/new", children: t("posts.list.createFirst") }) })
384
- ] }) : /* @__PURE__ */ jsx4("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs3(Table, { children: [
385
- /* @__PURE__ */ jsx4(TableHeader, { children: /* @__PURE__ */ jsxs3(TableRow, { children: [
386
- /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnTitle") }),
387
- /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnStatus") }),
388
- /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnSlug") }),
389
- /* @__PURE__ */ jsx4(TableHead, { children: t("posts.list.columnUpdated") })
390
- ] }) }),
391
- /* @__PURE__ */ jsx4(TableBody, { children: posts.map((post) => /* @__PURE__ */ jsxs3(TableRow, { children: [
392
- /* @__PURE__ */ jsx4(TableCell, { children: /* @__PURE__ */ jsx4(
393
- Link2,
394
- {
395
- href: `/admin/posts/${post.postId}`,
396
- className: "font-medium hover:underline",
397
- children: post.title
398
- }
399
- ) }),
400
- /* @__PURE__ */ jsx4(TableCell, { children: /* @__PURE__ */ jsx4(
401
- "span",
402
- {
403
- className: post.status === "published" ? "inline-block rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700" : "inline-block rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-700",
404
- children: t(`common.${post.status}`)
405
- }
406
- ) }),
407
- /* @__PURE__ */ jsx4(TableCell, { className: "font-mono text-xs text-muted-foreground", children: post.slug }),
408
- /* @__PURE__ */ jsx4(TableCell, { className: "text-sm text-muted-foreground", children: post.publishedAt ? new Date(post.publishedAt).toLocaleDateString() : "\u2014" })
409
- ] }, post.postId)) })
410
- ] }) })
411
- ] });
412
- }
413
65
  function createPostsListPage(_admin) {
414
66
  return PostsList;
415
67
  }
416
68
 
417
69
  // src/pages/post-new.tsx
418
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
419
- function NewPostPage() {
420
- const t = useT();
421
- return /* @__PURE__ */ jsxs4("div", { className: "p-8", children: [
422
- /* @__PURE__ */ jsx5("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.newTitle") }),
423
- /* @__PURE__ */ jsx5(PostForm, {})
424
- ] });
425
- }
426
70
  function createNewPostPage(_admin) {
427
71
  return NewPostPage;
428
72
  }
429
73
 
430
74
  // src/pages/post-edit.tsx
431
- import { useEffect as useEffect4, useState as useState3, use } from "react";
432
- import { notFound } from "next/navigation";
433
- import { getPostById } from "ampless";
434
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
435
- function EditPostPage({ params }) {
436
- const t = useT();
437
- const { postId } = use(params);
438
- const [post, setPost] = useState3(null);
439
- const [loading, setLoading] = useState3(true);
440
- const [missing, setMissing] = useState3(false);
441
- useEffect4(() => {
442
- const siteId = readAdminSiteIdFromCookie();
443
- getPostById(postId, { siteId }).then((p) => {
444
- if (!p) setMissing(true);
445
- else setPost(p);
446
- }).finally(() => setLoading(false));
447
- }, [postId]);
448
- if (loading) return /* @__PURE__ */ jsx6("div", { className: "p-8", children: t("common.loading") });
449
- if (missing) notFound();
450
- return /* @__PURE__ */ jsxs5("div", { className: "p-8", children: [
451
- /* @__PURE__ */ jsx6("h1", { className: "mb-8 text-3xl font-bold", children: t("posts.form.editTitle") }),
452
- post && /* @__PURE__ */ jsx6(PostForm, { post })
453
- ] });
454
- }
455
75
  function createEditPostPage(_admin) {
456
76
  return EditPostPage;
457
77
  }
458
78
 
459
79
  // src/pages/media.tsx
460
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
461
- function MediaPage() {
462
- const t = useT();
463
- return /* @__PURE__ */ jsxs6("div", { className: "p-8", children: [
464
- /* @__PURE__ */ jsx7("h1", { className: "mb-8 text-3xl font-bold", children: t("media.title") }),
465
- /* @__PURE__ */ jsx7(MediaUploader, {})
466
- ] });
467
- }
468
80
  function createMediaPage(_admin) {
469
81
  return MediaPage;
470
82
  }
471
83
 
472
84
  // src/pages/sites-list.tsx
473
- import Link3 from "next/link";
85
+ import Link from "next/link";
474
86
  import { DEFAULT_SITE_ID, isMultiSite, siteFor } from "ampless";
475
87
  import {
476
- Button as Button3,
477
- Table as Table2,
478
- TableBody as TableBody2,
479
- TableCell as TableCell2,
480
- TableHead as TableHead2,
481
- TableHeader as TableHeader2,
482
- TableRow as TableRow2
88
+ Button,
89
+ Table,
90
+ TableBody,
91
+ TableCell,
92
+ TableHead,
93
+ TableHeader,
94
+ TableRow
483
95
  } from "@ampless/runtime/ui";
484
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
96
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
485
97
  function createSitesListPage(admin) {
486
98
  const { cmsConfig, t } = admin;
487
99
  async function SitesPage() {
488
100
  const multi = isMultiSite(cmsConfig);
489
101
  const ids = multi ? Object.keys(cmsConfig.sites ?? {}) : [DEFAULT_SITE_ID];
490
- return /* @__PURE__ */ jsxs7("div", { className: "p-8", children: [
491
- /* @__PURE__ */ jsx8("div", { className: "mb-8 flex items-center justify-between", children: /* @__PURE__ */ jsxs7("div", { children: [
492
- /* @__PURE__ */ jsx8("h1", { className: "text-3xl font-bold", children: t("sites.list.title") }),
493
- /* @__PURE__ */ jsx8("p", { className: "mt-1 text-sm text-muted-foreground", children: t("sites.list.description") })
102
+ return /* @__PURE__ */ jsxs2("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
103
+ /* @__PURE__ */ jsx2("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3 md:mb-8", children: /* @__PURE__ */ jsxs2("div", { children: [
104
+ /* @__PURE__ */ jsx2("h1", { className: "text-2xl font-bold md:text-3xl", children: t("sites.list.title") }),
105
+ /* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-muted-foreground", children: t("sites.list.description") })
494
106
  ] }) }),
495
- /* @__PURE__ */ jsx8("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs7(Table2, { children: [
496
- /* @__PURE__ */ jsx8(TableHeader2, { children: /* @__PURE__ */ jsxs7(TableRow2, { children: [
497
- /* @__PURE__ */ jsx8(TableHead2, { children: t("sites.list.columnSiteId") }),
498
- /* @__PURE__ */ jsx8(TableHead2, { children: t("sites.list.columnName") }),
499
- /* @__PURE__ */ jsx8(TableHead2, { children: t("sites.list.columnUrl") }),
500
- /* @__PURE__ */ jsx8(TableHead2, { children: t("sites.list.columnDomains") }),
501
- /* @__PURE__ */ jsx8(TableHead2, {})
107
+ /* @__PURE__ */ jsx2("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs2(Table, { children: [
108
+ /* @__PURE__ */ jsx2(TableHeader, { children: /* @__PURE__ */ jsxs2(TableRow, { children: [
109
+ /* @__PURE__ */ jsx2(TableHead, { children: t("sites.list.columnSiteId") }),
110
+ /* @__PURE__ */ jsx2(TableHead, { children: t("sites.list.columnName") }),
111
+ /* @__PURE__ */ jsx2(TableHead, { children: t("sites.list.columnUrl") }),
112
+ /* @__PURE__ */ jsx2(TableHead, { children: t("sites.list.columnDomains") }),
113
+ /* @__PURE__ */ jsx2(TableHead, {})
502
114
  ] }) }),
503
- /* @__PURE__ */ jsx8(TableBody2, { children: ids.map((id) => {
115
+ /* @__PURE__ */ jsx2(TableBody, { children: ids.map((id) => {
504
116
  const site = siteFor(id, cmsConfig);
505
117
  const domains = cmsConfig.sites?.[id]?.domains ?? [];
506
- return /* @__PURE__ */ jsxs7(TableRow2, { children: [
507
- /* @__PURE__ */ jsx8(TableCell2, { className: "font-mono text-xs", children: id }),
508
- /* @__PURE__ */ jsx8(TableCell2, { className: "font-medium", children: site.name }),
509
- /* @__PURE__ */ jsx8(TableCell2, { className: "text-sm text-muted-foreground", children: site.url }),
510
- /* @__PURE__ */ jsx8(TableCell2, { className: "text-sm text-muted-foreground", children: domains.length > 0 ? domains.join(", ") : "\u2014" }),
511
- /* @__PURE__ */ jsx8(TableCell2, { children: /* @__PURE__ */ jsx8(Button3, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx8(Link3, { href: `/admin/sites/${id}`, children: t("sites.list.edit") }) }) })
118
+ return /* @__PURE__ */ jsxs2(TableRow, { children: [
119
+ /* @__PURE__ */ jsx2(TableCell, { className: "font-mono text-xs", children: id }),
120
+ /* @__PURE__ */ jsx2(TableCell, { className: "font-medium", children: site.name }),
121
+ /* @__PURE__ */ jsx2(TableCell, { className: "text-sm text-muted-foreground", children: site.url }),
122
+ /* @__PURE__ */ jsx2(TableCell, { className: "text-sm text-muted-foreground", children: domains.length > 0 ? domains.join(", ") : "\u2014" }),
123
+ /* @__PURE__ */ jsx2(TableCell, { children: /* @__PURE__ */ jsx2(Button, { asChild: true, variant: "outline", size: "sm", children: /* @__PURE__ */ jsx2(Link, { href: `/admin/sites/${id}`, children: t("sites.list.edit") }) }) })
512
124
  ] }, id);
513
125
  }) })
514
126
  ] }) })
@@ -518,9 +130,9 @@ function createSitesListPage(admin) {
518
130
  }
519
131
 
520
132
  // src/pages/site-edit.tsx
521
- import Link4 from "next/link";
133
+ import Link2 from "next/link";
522
134
  import { siteFor as siteFor2 } from "ampless";
523
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
135
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
524
136
  function createSiteEditPage(admin) {
525
137
  const { cmsConfig, t, loadSiteSettings } = admin;
526
138
  async function EditSitePage({ params }) {
@@ -545,10 +157,10 @@ function createSiteEditPage(admin) {
545
157
  dateFormat: settings.dateFormat,
546
158
  timezone: settings.timezone
547
159
  };
548
- return /* @__PURE__ */ jsxs8("div", { className: "p-8", children: [
549
- /* @__PURE__ */ jsxs8("div", { className: "mb-8", children: [
550
- /* @__PURE__ */ jsxs8(
551
- Link4,
160
+ return /* @__PURE__ */ jsxs3("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
161
+ /* @__PURE__ */ jsxs3("div", { className: "mb-6 md:mb-8", children: [
162
+ /* @__PURE__ */ jsxs3(
163
+ Link2,
552
164
  {
553
165
  href: "/admin/sites",
554
166
  className: "text-sm text-muted-foreground hover:underline",
@@ -558,14 +170,14 @@ function createSiteEditPage(admin) {
558
170
  ]
559
171
  }
560
172
  ),
561
- /* @__PURE__ */ jsx9("h1", { className: "mt-2 text-3xl font-bold", children: settings.site.name }),
562
- /* @__PURE__ */ jsxs8("p", { className: "text-sm text-muted-foreground", children: [
173
+ /* @__PURE__ */ jsx3("h1", { className: "mt-2 text-2xl font-bold md:text-3xl", children: settings.site.name }),
174
+ /* @__PURE__ */ jsxs3("p", { className: "text-sm text-muted-foreground", children: [
563
175
  t("common.siteId"),
564
176
  ": ",
565
- /* @__PURE__ */ jsx9("code", { className: "font-mono", children: siteId })
177
+ /* @__PURE__ */ jsx3("code", { className: "font-mono", children: siteId })
566
178
  ] }),
567
- /* @__PURE__ */ jsx9("div", { className: "mt-4", children: /* @__PURE__ */ jsx9(
568
- Link4,
179
+ /* @__PURE__ */ jsx3("div", { className: "mt-4", children: /* @__PURE__ */ jsx3(
180
+ Link2,
569
181
  {
570
182
  href: `/admin/sites/${siteId}/theme`,
571
183
  className: "text-sm font-medium underline",
@@ -573,16 +185,16 @@ function createSiteEditPage(admin) {
573
185
  }
574
186
  ) })
575
187
  ] }),
576
- /* @__PURE__ */ jsx9(SiteSettingsForm, { siteId, initial, fallback })
188
+ /* @__PURE__ */ jsx3(SiteSettingsForm, { siteId, initial, fallback })
577
189
  ] });
578
190
  }
579
191
  return EditSitePage;
580
192
  }
581
193
 
582
194
  // src/pages/site-theme.tsx
583
- import Link5 from "next/link";
195
+ import Link3 from "next/link";
584
196
  import { siteFor as siteFor3, resolveLocalized } from "ampless";
585
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
197
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
586
198
  function createSiteThemePage(admin, themeList) {
587
199
  const { cmsConfig, t, locale, loadThemeConfig } = admin;
588
200
  async function ThemePage({ params }) {
@@ -594,10 +206,10 @@ function createSiteThemePage(admin, themeList) {
594
206
  label: m.manifest.label,
595
207
  description: m.manifest.description
596
208
  }));
597
- return /* @__PURE__ */ jsxs9("div", { className: "p-8", children: [
598
- /* @__PURE__ */ jsxs9("div", { className: "mb-8", children: [
599
- /* @__PURE__ */ jsxs9(
600
- Link5,
209
+ return /* @__PURE__ */ jsxs4("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
210
+ /* @__PURE__ */ jsxs4("div", { className: "mb-6 md:mb-8", children: [
211
+ /* @__PURE__ */ jsxs4(
212
+ Link3,
601
213
  {
602
214
  href: `/admin/sites/${siteId}`,
603
215
  className: "text-sm text-muted-foreground hover:underline",
@@ -607,18 +219,18 @@ function createSiteThemePage(admin, themeList) {
607
219
  ]
608
220
  }
609
221
  ),
610
- /* @__PURE__ */ jsx10("h1", { className: "mt-2 text-3xl font-bold", children: t("theme.title") }),
611
- /* @__PURE__ */ jsxs9("p", { className: "text-sm text-muted-foreground", children: [
222
+ /* @__PURE__ */ jsx4("h1", { className: "mt-2 text-2xl font-bold md:text-3xl", children: t("theme.title") }),
223
+ /* @__PURE__ */ jsxs4("p", { className: "text-sm text-muted-foreground", children: [
612
224
  t("common.active"),
613
225
  ":",
614
226
  " ",
615
- /* @__PURE__ */ jsx10("strong", { children: resolveLocalized(theme.manifest.label, locale) }),
227
+ /* @__PURE__ */ jsx4("strong", { children: resolveLocalized(theme.manifest.label, locale) }),
616
228
  " (",
617
229
  theme.activeTheme,
618
230
  ")"
619
231
  ] })
620
232
  ] }),
621
- /* @__PURE__ */ jsx10(
233
+ /* @__PURE__ */ jsx4(
622
234
  ThemeSettingsForm,
623
235
  {
624
236
  siteId,
@@ -633,195 +245,21 @@ function createSiteThemePage(admin, themeList) {
633
245
  return ThemePage;
634
246
  }
635
247
 
636
- // src/pages/login.tsx
637
- import { useState as useState4 } from "react";
638
- import { useRouter } from "next/navigation";
639
- import {
640
- signIn,
641
- signUp,
642
- confirmSignUp,
643
- resetPassword,
644
- confirmResetPassword
645
- } from "aws-amplify/auth";
646
- import {
647
- Button as Button4,
648
- Input,
649
- Label,
650
- Card as Card2,
651
- CardContent as CardContent2,
652
- CardHeader as CardHeader2,
653
- CardTitle as CardTitle2,
654
- CardDescription
655
- } from "@ampless/runtime/ui";
656
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
657
- function LoginPage() {
658
- const router = useRouter();
659
- const t = useT();
660
- const [mode, setMode] = useState4("signIn");
661
- const [email, setEmail] = useState4("");
662
- const [password, setPassword] = useState4("");
663
- const [code, setCode] = useState4("");
664
- const [error, setError] = useState4(null);
665
- const [info, setInfo] = useState4(null);
666
- const [loading, setLoading] = useState4(false);
667
- function go(next) {
668
- setMode(next);
669
- setError(null);
670
- setInfo(null);
671
- setCode("");
672
- if (next === "signIn" || next === "signUp" || next === "forgot") setPassword("");
673
- }
674
- async function handleSubmit(e) {
675
- e.preventDefault();
676
- setError(null);
677
- setInfo(null);
678
- setLoading(true);
679
- try {
680
- if (mode === "signIn") {
681
- const result = await signIn({ username: email, password });
682
- if (result.isSignedIn) {
683
- router.push("/admin");
684
- router.refresh();
685
- } else {
686
- setError(t("auth.additionalStep", { step: result.nextStep.signInStep }));
687
- }
688
- } else if (mode === "signUp") {
689
- await signUp({
690
- username: email,
691
- password,
692
- options: { userAttributes: { email } }
693
- });
694
- go("confirm");
695
- } else if (mode === "confirm") {
696
- await confirmSignUp({ username: email, confirmationCode: code });
697
- const result = await signIn({ username: email, password });
698
- if (result.isSignedIn) {
699
- router.push("/admin");
700
- router.refresh();
701
- }
702
- } else if (mode === "forgot") {
703
- await resetPassword({ username: email });
704
- setMode("reset");
705
- setInfo(t("auth.forgot.codeSent"));
706
- } else if (mode === "reset") {
707
- await confirmResetPassword({
708
- username: email,
709
- confirmationCode: code,
710
- newPassword: password
711
- });
712
- const result = await signIn({ username: email, password });
713
- if (result.isSignedIn) {
714
- router.push("/admin");
715
- router.refresh();
716
- } else {
717
- setMode("signIn");
718
- setInfo(t("auth.reset.passwordUpdated"));
719
- }
720
- }
721
- } catch (err) {
722
- setError(err instanceof Error ? err.message : String(err));
723
- } finally {
724
- setLoading(false);
248
+ // src/pages/users-list.tsx
249
+ import { redirect as redirect2 } from "next/navigation";
250
+ import { jsx as jsx5 } from "react/jsx-runtime";
251
+ function createUsersListPage(admin) {
252
+ async function UsersPage() {
253
+ const session = await admin.getServerSession();
254
+ if (!admin.isAdmin(session)) {
255
+ redirect2("/admin");
725
256
  }
257
+ return /* @__PURE__ */ jsx5(UsersListView, { currentUserId: session.userId });
726
258
  }
727
- const showEmail = mode !== "confirm" && mode !== "reset";
728
- const showPassword = mode === "signIn" || mode === "signUp" || mode === "reset";
729
- const showCode = mode === "confirm" || mode === "reset";
730
- return /* @__PURE__ */ jsx11("main", { className: "flex min-h-screen items-center justify-center bg-muted/30 p-4", children: /* @__PURE__ */ jsxs10(Card2, { className: "w-full max-w-md", children: [
731
- /* @__PURE__ */ jsxs10(CardHeader2, { children: [
732
- /* @__PURE__ */ jsx11(CardTitle2, { children: t(`auth.${mode}.title`) }),
733
- /* @__PURE__ */ jsx11(CardDescription, { children: t(`auth.${mode}.description`) })
734
- ] }),
735
- /* @__PURE__ */ jsx11(CardContent2, { children: /* @__PURE__ */ jsxs10("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
736
- showEmail && /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
737
- /* @__PURE__ */ jsx11(Label, { htmlFor: "email", children: t("auth.common.email") }),
738
- /* @__PURE__ */ jsx11(
739
- Input,
740
- {
741
- id: "email",
742
- type: "email",
743
- required: true,
744
- value: email,
745
- onChange: (e) => setEmail(e.target.value),
746
- autoComplete: "email"
747
- }
748
- )
749
- ] }),
750
- showCode && /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
751
- /* @__PURE__ */ jsx11(Label, { htmlFor: "code", children: t("auth.common.code") }),
752
- /* @__PURE__ */ jsx11(
753
- Input,
754
- {
755
- id: "code",
756
- required: true,
757
- value: code,
758
- onChange: (e) => setCode(e.target.value),
759
- autoComplete: "one-time-code"
760
- }
761
- )
762
- ] }),
763
- showPassword && /* @__PURE__ */ jsxs10("div", { className: "space-y-2", children: [
764
- /* @__PURE__ */ jsx11(Label, { htmlFor: "password", children: mode === "reset" ? t("auth.common.newPassword") : t("auth.common.password") }),
765
- /* @__PURE__ */ jsx11(
766
- Input,
767
- {
768
- id: "password",
769
- type: "password",
770
- required: true,
771
- minLength: 8,
772
- value: password,
773
- onChange: (e) => setPassword(e.target.value),
774
- autoComplete: mode === "signIn" ? "current-password" : "new-password"
775
- }
776
- ),
777
- (mode === "signUp" || mode === "reset") && /* @__PURE__ */ jsx11("p", { className: "text-xs text-muted-foreground", children: t("auth.common.passwordHint") })
778
- ] }),
779
- info && /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: info }),
780
- error && /* @__PURE__ */ jsx11("p", { className: "text-sm text-destructive", children: error }),
781
- /* @__PURE__ */ jsx11(Button4, { type: "submit", className: "w-full", disabled: loading, children: loading ? t("auth.common.working") : t(`auth.${mode}.submit`) }),
782
- /* @__PURE__ */ jsxs10("div", { className: "space-y-1 text-center text-sm", children: [
783
- mode === "signIn" && /* @__PURE__ */ jsxs10(Fragment2, { children: [
784
- /* @__PURE__ */ jsx11("p", { children: /* @__PURE__ */ jsx11(
785
- "button",
786
- {
787
- type: "button",
788
- className: "text-primary hover:underline",
789
- onClick: () => go("forgot"),
790
- children: t("auth.signIn.forgotPassword")
791
- }
792
- ) }),
793
- /* @__PURE__ */ jsx11("p", { children: /* @__PURE__ */ jsx11(
794
- "button",
795
- {
796
- type: "button",
797
- className: "text-primary hover:underline",
798
- onClick: () => go("signUp"),
799
- children: t("auth.signIn.createAccount")
800
- }
801
- ) })
802
- ] }),
803
- (mode === "signUp" || mode === "forgot" || mode === "reset") && /* @__PURE__ */ jsx11("p", { children: /* @__PURE__ */ jsx11(
804
- "button",
805
- {
806
- type: "button",
807
- className: "text-primary hover:underline",
808
- onClick: () => go("signIn"),
809
- children: t("auth.signUp.backToSignIn")
810
- }
811
- ) }),
812
- mode === "reset" && /* @__PURE__ */ jsx11("p", { children: /* @__PURE__ */ jsx11(
813
- "button",
814
- {
815
- type: "button",
816
- className: "text-primary hover:underline",
817
- onClick: () => go("forgot"),
818
- children: t("auth.reset.resendCode")
819
- }
820
- ) })
821
- ] })
822
- ] }) })
823
- ] }) });
259
+ return UsersPage;
824
260
  }
261
+
262
+ // src/pages/login.tsx
825
263
  function createLoginPage(_admin) {
826
264
  return LoginPage;
827
265
  }
@@ -835,5 +273,6 @@ export {
835
273
  createPostsListPage,
836
274
  createSiteEditPage,
837
275
  createSiteThemePage,
838
- createSitesListPage
276
+ createSitesListPage,
277
+ createUsersListPage
839
278
  };