@ampless/admin 1.0.0-alpha.85 → 1.0.0-beta.101

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.
Files changed (43) hide show
  1. package/README.ja.md +2 -2
  2. package/README.md +2 -2
  3. package/dist/api/index.d.ts +1 -1
  4. package/dist/api/index.js +4 -2
  5. package/dist/{chunk-SQ6LFPXH.js → chunk-2TBGPB5H.js} +2 -2
  6. package/dist/{chunk-K44QF3MV.js → chunk-4OFRX6QX.js} +1 -1
  7. package/dist/{chunk-HANIMWMN.js → chunk-4WYMEUYU.js} +301 -222
  8. package/dist/chunk-7HSWOQWV.js +172 -0
  9. package/dist/{chunk-J6R356H3.js → chunk-7OZ23MLD.js} +1 -1
  10. package/dist/{chunk-OFPNBZKP.js → chunk-7W7LAQXY.js} +76 -8
  11. package/dist/{chunk-HN3UFHP7.js → chunk-ANME5KDN.js} +1 -1
  12. package/dist/{chunk-GGHN2RKQ.js → chunk-E5VF3Y65.js} +69 -3
  13. package/dist/{chunk-7LT6YPIT.js → chunk-JLCTF5HY.js} +8 -2
  14. package/dist/{chunk-R3TEJ32M.js → chunk-LXY3BRKQ.js} +2 -2
  15. package/dist/{chunk-MKWYNK4J.js → chunk-NHMG2MRJ.js} +2 -2
  16. package/dist/{chunk-AYESATAM.js → chunk-OPVLQOOB.js} +44 -13
  17. package/dist/{chunk-SMI7Y77B.js → chunk-ORXOMW7M.js} +265 -190
  18. package/dist/{chunk-W4VHP2JW.js → chunk-SYAEM5DM.js} +1 -1
  19. package/dist/{chunk-7NQERXNY.js → chunk-TJZ4FTU6.js} +1 -1
  20. package/dist/{chunk-CFIOUXVE.js → chunk-V5XORHKX.js} +1 -1
  21. package/dist/chunk-ZDIDJCSU.js +40 -0
  22. package/dist/components/account-view.d.ts +8 -0
  23. package/dist/components/account-view.js +10 -0
  24. package/dist/components/admin-dashboard.js +3 -3
  25. package/dist/components/edit-post-view.js +5 -5
  26. package/dist/components/index.d.ts +4 -2
  27. package/dist/components/index.js +7 -7
  28. package/dist/components/login-view.d.ts +3 -1
  29. package/dist/components/login-view.js +4 -3
  30. package/dist/components/mcp-tokens-view.js +3 -3
  31. package/dist/components/media-view.js +5 -5
  32. package/dist/components/new-post-view.js +5 -5
  33. package/dist/components/plugin-settings-form.d.ts +2 -10
  34. package/dist/components/plugin-settings-form.js +5 -7
  35. package/dist/components/posts-list-view.js +3 -3
  36. package/dist/components/users-list-view.js +3 -3
  37. package/dist/{i18n-DA-aakRX.d.ts → i18n-Bd8q9aTl.d.ts} +108 -5
  38. package/dist/index.d.ts +2 -2
  39. package/dist/index.js +1 -1
  40. package/dist/metafile-esm.json +1 -1
  41. package/dist/pages/index.d.ts +21 -4
  42. package/dist/pages/index.js +54 -24
  43. package/package.json +5 -6
@@ -0,0 +1,172 @@
1
+ 'use client';
2
+ import {
3
+ isWebAuthnSupported
4
+ } from "./chunk-ZDIDJCSU.js";
5
+ import {
6
+ useT
7
+ } from "./chunk-4OFRX6QX.js";
8
+
9
+ // src/components/account-view.tsx
10
+ import { useEffect, useState } from "react";
11
+ import {
12
+ Button,
13
+ Table,
14
+ TableBody,
15
+ TableCell,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow
19
+ } from "@ampless/runtime/ui";
20
+
21
+ // src/lib/passkey-store.ts
22
+ import {
23
+ associateWebAuthnCredential,
24
+ listWebAuthnCredentials,
25
+ deleteWebAuthnCredential
26
+ } from "aws-amplify/auth";
27
+ var defaultPasskeyApi = {
28
+ async register() {
29
+ await associateWebAuthnCredential();
30
+ },
31
+ async list() {
32
+ const credentials = [];
33
+ let nextToken;
34
+ do {
35
+ const page = await listWebAuthnCredentials(nextToken ? { nextToken } : void 0);
36
+ for (const cred of page.credentials) {
37
+ if (!cred.credentialId) continue;
38
+ credentials.push({
39
+ credentialId: cred.credentialId,
40
+ friendlyName: cred.friendlyCredentialName,
41
+ createdAt: cred.createdAt ? cred.createdAt.toISOString() : null
42
+ });
43
+ }
44
+ nextToken = page.nextToken;
45
+ } while (nextToken);
46
+ credentials.sort((a, b) => {
47
+ if (!a.createdAt) return 1;
48
+ if (!b.createdAt) return -1;
49
+ return b.createdAt.localeCompare(a.createdAt);
50
+ });
51
+ return credentials;
52
+ },
53
+ async remove(credentialId) {
54
+ await deleteWebAuthnCredential({ credentialId });
55
+ }
56
+ };
57
+ var api = defaultPasskeyApi;
58
+ function getPasskeyApi() {
59
+ return api;
60
+ }
61
+
62
+ // src/components/account-view.tsx
63
+ import { jsx, jsxs } from "react/jsx-runtime";
64
+ function formatDate(iso) {
65
+ if (!iso) return "";
66
+ const d = new Date(iso);
67
+ if (Number.isNaN(d.getTime())) return "";
68
+ return d.toLocaleString();
69
+ }
70
+ function AccountView({ currentUserEmail, passkeysEnabled }) {
71
+ const t = useT();
72
+ const [supported, setSupported] = useState(false);
73
+ const [supportResolved, setSupportResolved] = useState(false);
74
+ const [passkeys, setPasskeys] = useState(null);
75
+ const [loading, setLoading] = useState(true);
76
+ const [loadError, setLoadError] = useState(null);
77
+ const [adding, setAdding] = useState(false);
78
+ const [addError, setAddError] = useState(null);
79
+ async function loadPasskeys() {
80
+ setLoading(true);
81
+ setLoadError(null);
82
+ try {
83
+ const list = await getPasskeyApi().list();
84
+ setPasskeys(list);
85
+ } catch (err) {
86
+ console.error("[account-view] list passkeys failed", err);
87
+ setLoadError(err instanceof Error ? err.message : String(err));
88
+ } finally {
89
+ setLoading(false);
90
+ }
91
+ }
92
+ useEffect(() => {
93
+ if (!passkeysEnabled) {
94
+ setSupportResolved(true);
95
+ setLoading(false);
96
+ return;
97
+ }
98
+ const ok = isWebAuthnSupported();
99
+ setSupported(ok);
100
+ setSupportResolved(true);
101
+ if (ok) void loadPasskeys();
102
+ else setLoading(false);
103
+ }, [passkeysEnabled]);
104
+ async function handleAdd() {
105
+ setAdding(true);
106
+ setAddError(null);
107
+ try {
108
+ await getPasskeyApi().register();
109
+ await loadPasskeys();
110
+ } catch (err) {
111
+ console.error("[account-view] register passkey failed", err);
112
+ setAddError(err instanceof Error ? err.message : String(err));
113
+ } finally {
114
+ setAdding(false);
115
+ }
116
+ }
117
+ async function handleRemove(credentialId) {
118
+ if (!confirm(t("account.passkeys.deleteConfirm"))) return;
119
+ try {
120
+ await getPasskeyApi().remove(credentialId);
121
+ await loadPasskeys();
122
+ } catch (err) {
123
+ console.error("[account-view] remove passkey failed", err);
124
+ alert(err instanceof Error ? err.message : String(err));
125
+ }
126
+ }
127
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-4xl space-y-8 p-4 md:p-8", children: [
128
+ /* @__PURE__ */ jsxs("div", { children: [
129
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold md:text-3xl", children: t("account.title") }),
130
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: currentUserEmail })
131
+ ] }),
132
+ /* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-md border bg-card p-4 md:p-6", children: [
133
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
134
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: t("account.passkeys.title") }) }),
135
+ passkeysEnabled && supportResolved && supported && /* @__PURE__ */ jsx(Button, { type: "button", disabled: adding, onClick: () => void handleAdd(), children: adding ? t("account.passkeys.adding") : t("account.passkeys.add") })
136
+ ] }),
137
+ addError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: addError }),
138
+ !passkeysEnabled ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("account.passkeys.disabled") }) : supportResolved && !supported ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("account.passkeys.unsupported") }) : loading ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("account.passkeys.loading") }) : loadError ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
139
+ t("account.passkeys.loadError"),
140
+ ": ",
141
+ loadError
142
+ ] }) : !passkeys || passkeys.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("account.passkeys.empty") }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-md border", children: /* @__PURE__ */ jsxs(Table, { children: [
143
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
144
+ /* @__PURE__ */ jsx(TableHead, { children: t("account.passkeys.columnName") }),
145
+ /* @__PURE__ */ jsx(TableHead, { children: t("account.passkeys.columnCreated") }),
146
+ /* @__PURE__ */ jsx(TableHead, { className: "w-[1%] whitespace-nowrap", children: t("common.actions") })
147
+ ] }) }),
148
+ /* @__PURE__ */ jsx(TableBody, { children: passkeys.map((pk) => /* @__PURE__ */ jsxs(TableRow, { children: [
149
+ /* @__PURE__ */ jsx(TableCell, { className: "font-medium", children: pk.friendlyName || /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
150
+ pk.credentialId.slice(0, 12),
151
+ "\u2026"
152
+ ] }) }),
153
+ /* @__PURE__ */ jsx(TableCell, { className: "text-xs text-muted-foreground", children: pk.createdAt ? /* @__PURE__ */ jsx("span", { title: pk.createdAt, children: formatDate(pk.createdAt) }) : "" }),
154
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(
155
+ Button,
156
+ {
157
+ type: "button",
158
+ variant: "outline",
159
+ size: "sm",
160
+ onClick: () => void handleRemove(pk.credentialId),
161
+ children: t("account.passkeys.delete")
162
+ }
163
+ ) })
164
+ ] }, pk.credentialId)) })
165
+ ] }) })
166
+ ] })
167
+ ] });
168
+ }
169
+
170
+ export {
171
+ AccountView
172
+ };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-K44QF3MV.js";
4
+ } from "./chunk-4OFRX6QX.js";
5
5
 
6
6
  // src/components/posts-list-view.tsx
7
7
  import { useEffect, useMemo, useState } from "react";
@@ -8,6 +8,7 @@ var en_default = {
8
8
  edit: "Edit",
9
9
  back: "Back",
10
10
  loading: "Loading...",
11
+ loadError: "Failed to load.",
11
12
  default: "Default",
12
13
  remove: "Remove",
13
14
  create: "Create",
@@ -41,7 +42,8 @@ var en_default = {
41
42
  signOut: "Sign out",
42
43
  site: "Site",
43
44
  openMenu: "Open menu",
44
- closeMenu: "Close menu"
45
+ closeMenu: "Close menu",
46
+ account: "Account"
45
47
  },
46
48
  dashboard: {
47
49
  title: "Dashboard",
@@ -88,7 +90,7 @@ var en_default = {
88
90
  excerpt: "Excerpt",
89
91
  body: "Body",
90
92
  format: "Format",
91
- formatHint: "Tiptap is the rich editor. Markdown / HTML use a plain textarea. Tip: a slug ending in .html (e.g. promo.html) serves the post as a bare HTML response with no theme chrome.",
93
+ formatHint: "Tiptap is the rich editor. Markdown / HTML use a plain textarea.",
92
94
  insertMedia: "Insert media",
93
95
  tabEdit: "Edit",
94
96
  tabPreview: "Preview",
@@ -277,6 +279,10 @@ var en_default = {
277
279
  deleteColumn: "Delete column",
278
280
  toggleHeaderRow: "Toggle header row",
279
281
  deleteTable: "Delete table"
282
+ },
283
+ codeBlock: {
284
+ languageLabel: "Language",
285
+ languagePlaceholder: "e.g. mermaid, ts"
280
286
  }
281
287
  },
282
288
  auth: {
@@ -293,7 +299,8 @@ var en_default = {
293
299
  description: "Sign in to manage your site.",
294
300
  submit: "Sign in",
295
301
  forgotPassword: "Forgot password?",
296
- createAccount: "Create admin account"
302
+ createAccount: "Create admin account",
303
+ passkeyButton: "Sign in with passkey"
297
304
  },
298
305
  signUp: {
299
306
  title: "Create admin account",
@@ -324,6 +331,14 @@ var en_default = {
324
331
  invalidCredentials: "Invalid email or password.",
325
332
  passwordTooShort: "Password must be at least 8 characters.",
326
333
  userExists: "An account with this email already exists."
334
+ },
335
+ passkey: {
336
+ emailRequired: "Enter your email first, then sign in with your passkey.",
337
+ noneRegistered: "No passkey is registered for this account yet. Sign in with your password, then add one from the account page.",
338
+ cancelled: "Passkey sign-in was cancelled.",
339
+ unsupported: "This browser does not support passkeys.",
340
+ rpMismatch: "This site isn't configured for passkeys on this domain. Ask the site engineer to set the relyingPartyId in amplify/auth/resource.custom.ts.",
341
+ failed: "Passkey sign-in failed. Try again, or sign in with your password."
327
342
  }
328
343
  },
329
344
  plugins: {
@@ -337,7 +352,8 @@ var en_default = {
337
352
  saveError: "Save failed",
338
353
  invalidValue: "One or more values are invalid \u2014 fix highlighted fields and try again.",
339
354
  resetToDefault: "Reset to default",
340
- resetDone: "Stored value cleared. Falling back to the manifest default."
355
+ resetDone: "Stored value cleared. Falling back to the manifest default.",
356
+ noChanges: "No changes to save."
341
357
  },
342
358
  mcpTokens: {
343
359
  title: "MCP tokens",
@@ -384,6 +400,24 @@ var en_default = {
384
400
  tagLabel: "Tag",
385
401
  permalink: "Permalink",
386
402
  shareOnX: "Share on X"
403
+ },
404
+ account: {
405
+ title: "Account",
406
+ passkeys: {
407
+ title: "Passkeys",
408
+ add: "Add passkey",
409
+ adding: "Adding...",
410
+ added: "Passkey added.",
411
+ loading: "Loading passkeys...",
412
+ empty: "No passkeys yet. Add one to sign in with Face ID, Touch ID, or your security key.",
413
+ columnName: "Name",
414
+ columnCreated: "Created",
415
+ delete: "Delete",
416
+ deleteConfirm: "Delete this passkey? You will no longer be able to sign in with it.",
417
+ loadError: "Failed to load passkeys",
418
+ unsupported: "This browser does not support passkeys.",
419
+ disabled: "Passkeys are disabled for this site."
420
+ }
387
421
  }
388
422
  };
389
423
 
@@ -397,6 +431,7 @@ var ja_default = {
397
431
  edit: "\u7DE8\u96C6",
398
432
  back: "\u623B\u308B",
399
433
  loading: "\u8AAD\u307F\u8FBC\u307F\u4E2D...",
434
+ loadError: "\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002",
400
435
  default: "\u30C7\u30D5\u30A9\u30EB\u30C8",
401
436
  remove: "\u524A\u9664",
402
437
  create: "\u4F5C\u6210",
@@ -430,7 +465,8 @@ var ja_default = {
430
465
  signOut: "\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8",
431
466
  site: "\u30B5\u30A4\u30C8",
432
467
  openMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u958B\u304F",
433
- closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B"
468
+ closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B",
469
+ account: "\u30A2\u30AB\u30A6\u30F3\u30C8"
434
470
  },
435
471
  dashboard: {
436
472
  title: "\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9",
@@ -477,7 +513,7 @@ var ja_default = {
477
513
  excerpt: "\u629C\u7C8B",
478
514
  body: "\u672C\u6587",
479
515
  format: "\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8",
480
- formatHint: "Tiptap \u306F\u30EA\u30C3\u30C1\u30A8\u30C7\u30A3\u30BF\u3002Markdown / HTML \u306F\u30D7\u30EC\u30FC\u30F3\u30C6\u30AD\u30B9\u30C8\u30A8\u30EA\u30A2\u3002\u30D2\u30F3\u30C8: \u30B9\u30E9\u30C3\u30B0\u306E\u672B\u5C3E\u304C .html (\u4F8B: promo.html) \u306E\u5834\u5408\u3001\u30C6\u30FC\u30DEchrome\u7121\u3057\u3067\u672C\u6587 HTML \u3092\u76F4\u63A5\u8FD4\u3057\u307E\u3059\u3002",
516
+ formatHint: "Tiptap \u306F\u30EA\u30C3\u30C1\u30A8\u30C7\u30A3\u30BF\u3002Markdown / HTML \u306F\u30D7\u30EC\u30FC\u30F3\u30C6\u30AD\u30B9\u30C8\u30A8\u30EA\u30A2\u3002",
481
517
  insertMedia: "\u30E1\u30C7\u30A3\u30A2\u3092\u633F\u5165",
482
518
  tabEdit: "\u7DE8\u96C6",
483
519
  tabPreview: "\u30D7\u30EC\u30D3\u30E5\u30FC",
@@ -666,6 +702,10 @@ var ja_default = {
666
702
  deleteColumn: "\u5217\u3092\u524A\u9664",
667
703
  toggleHeaderRow: "\u30D8\u30C3\u30C0\u30FC\u884C\u3092\u5207\u66FF",
668
704
  deleteTable: "\u30C6\u30FC\u30D6\u30EB\u3092\u524A\u9664"
705
+ },
706
+ codeBlock: {
707
+ languageLabel: "\u8A00\u8A9E",
708
+ languagePlaceholder: "\u4F8B: mermaid, ts"
669
709
  }
670
710
  },
671
711
  auth: {
@@ -682,7 +722,8 @@ var ja_default = {
682
722
  description: "\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u30B5\u30A4\u30C8\u3092\u7BA1\u7406\u3057\u307E\u3059\u3002",
683
723
  submit: "\u30B5\u30A4\u30F3\u30A4\u30F3",
684
724
  forgotPassword: "\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u304A\u5FD8\u308C\u3067\u3059\u304B?",
685
- createAccount: "\u7BA1\u7406\u8005\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210"
725
+ createAccount: "\u7BA1\u7406\u8005\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
726
+ passkeyButton: "\u30D1\u30B9\u30AD\u30FC\u3067\u30B5\u30A4\u30F3\u30A4\u30F3"
686
727
  },
687
728
  signUp: {
688
729
  title: "\u7BA1\u7406\u8005\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u4F5C\u6210",
@@ -713,6 +754,14 @@ var ja_default = {
713
754
  invalidCredentials: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u307E\u305F\u306F\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093\u3002",
714
755
  passwordTooShort: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F8\u6587\u5B57\u4EE5\u4E0A\u5FC5\u8981\u3067\u3059\u3002",
715
756
  userExists: "\u3053\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059\u3002"
757
+ },
758
+ passkey: {
759
+ emailRequired: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304B\u3089\u3001\u30D1\u30B9\u30AD\u30FC\u3067\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
760
+ noneRegistered: "\u3053\u306E\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u306F\u307E\u3060\u30D1\u30B9\u30AD\u30FC\u304C\u767B\u9332\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u30D1\u30B9\u30EF\u30FC\u30C9\u3067\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3001\u30A2\u30AB\u30A6\u30F3\u30C8\u30DA\u30FC\u30B8\u304B\u3089\u767B\u9332\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
761
+ cancelled: "\u30D1\u30B9\u30AD\u30FC\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F\u3002",
762
+ unsupported: "\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306F\u30D1\u30B9\u30AD\u30FC\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
763
+ rpMismatch: "\u3053\u306E\u30C9\u30E1\u30A4\u30F3\u3067\u306F\u30D1\u30B9\u30AD\u30FC\u304C\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u30B5\u30A4\u30C8\u30A8\u30F3\u30B8\u30CB\u30A2\u306B amplify/auth/resource.custom.ts \u306E relyingPartyId \u8A2D\u5B9A\u3092\u4F9D\u983C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
764
+ failed: "\u30D1\u30B9\u30AD\u30FC\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u3082\u3046\u4E00\u5EA6\u8A66\u3059\u304B\u3001\u30D1\u30B9\u30EF\u30FC\u30C9\u3067\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
716
765
  }
717
766
  },
718
767
  plugins: {
@@ -726,7 +775,8 @@ var ja_default = {
726
775
  saveError: "\u4FDD\u5B58\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
727
776
  invalidValue: "\u4E0D\u6B63\u306A\u5024\u304C\u542B\u307E\u308C\u3066\u3044\u307E\u3059 \u2014 \u30CF\u30A4\u30E9\u30A4\u30C8\u3055\u308C\u305F\u9805\u76EE\u3092\u4FEE\u6B63\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
728
777
  resetToDefault: "\u30C7\u30D5\u30A9\u30EB\u30C8\u306B\u623B\u3059",
729
- resetDone: "\u4FDD\u5B58\u5024\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3057\u305F\u3002\u30DE\u30CB\u30D5\u30A7\u30B9\u30C8\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u5024\u304C\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002"
778
+ resetDone: "\u4FDD\u5B58\u5024\u3092\u30AF\u30EA\u30A2\u3057\u307E\u3057\u305F\u3002\u30DE\u30CB\u30D5\u30A7\u30B9\u30C8\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u5024\u304C\u4F7F\u7528\u3055\u308C\u307E\u3059\u3002",
779
+ noChanges: "\u5909\u66F4\u306F\u3042\u308A\u307E\u305B\u3093\u3002"
730
780
  },
731
781
  mcpTokens: {
732
782
  title: "MCP \u30C8\u30FC\u30AF\u30F3",
@@ -773,6 +823,24 @@ var ja_default = {
773
823
  tagLabel: "\u30BF\u30B0",
774
824
  permalink: "\u30D1\u30FC\u30DE\u30EA\u30F3\u30AF",
775
825
  shareOnX: "X\u3067\u5171\u6709"
826
+ },
827
+ account: {
828
+ title: "\u30A2\u30AB\u30A6\u30F3\u30C8",
829
+ passkeys: {
830
+ title: "\u30D1\u30B9\u30AD\u30FC",
831
+ add: "\u30D1\u30B9\u30AD\u30FC\u3092\u8FFD\u52A0",
832
+ adding: "\u8FFD\u52A0\u4E2D...",
833
+ added: "\u30D1\u30B9\u30AD\u30FC\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F\u3002",
834
+ loading: "\u30D1\u30B9\u30AD\u30FC\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
835
+ empty: "\u30D1\u30B9\u30AD\u30FC\u306F\u307E\u3060\u3042\u308A\u307E\u305B\u3093\u3002\u8FFD\u52A0\u3059\u308B\u3068 Face ID\u30FBTouch ID\u30FB\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3\u30AD\u30FC\u3067\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u307E\u3059\u3002",
836
+ columnName: "\u540D\u524D",
837
+ columnCreated: "\u4F5C\u6210\u65E5",
838
+ delete: "\u524A\u9664",
839
+ deleteConfirm: "\u3053\u306E\u30D1\u30B9\u30AD\u30FC\u3092\u524A\u9664\u3057\u307E\u3059\u304B? \u3053\u306E\u30D1\u30B9\u30AD\u30FC\u3067\u306F\u30B5\u30A4\u30F3\u30A4\u30F3\u3067\u304D\u306A\u304F\u306A\u308A\u307E\u3059\u3002",
840
+ loadError: "\u30D1\u30B9\u30AD\u30FC\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
841
+ unsupported: "\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306F\u30D1\u30B9\u30AD\u30FC\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
842
+ disabled: "\u3053\u306E\u30B5\u30A4\u30C8\u3067\u306F\u30D1\u30B9\u30AD\u30FC\u306F\u7121\u52B9\u3067\u3059\u3002"
843
+ }
776
844
  }
777
845
  };
778
846
 
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-2ITWLRYF.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-K44QF3MV.js";
7
+ } from "./chunk-4OFRX6QX.js";
8
8
 
9
9
  // src/lib/upload.ts
10
10
  import { uploadData } from "aws-amplify/storage";
@@ -1,11 +1,17 @@
1
1
  'use client';
2
+ import {
3
+ classifyPasskeyError,
4
+ isWebAuthnSupported,
5
+ signInWithPasskey
6
+ } from "./chunk-ZDIDJCSU.js";
2
7
  import {
3
8
  useT
4
- } from "./chunk-K44QF3MV.js";
9
+ } from "./chunk-4OFRX6QX.js";
5
10
 
6
11
  // src/components/login-view.tsx
7
- import { useState } from "react";
12
+ import { useEffect, useRef, useState } from "react";
8
13
  import { useRouter } from "next/navigation";
14
+ import { Fingerprint } from "lucide-react";
9
15
  import {
10
16
  signIn,
11
17
  signUp,
@@ -24,7 +30,8 @@ import {
24
30
  CardDescription
25
31
  } from "@ampless/runtime/ui";
26
32
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
27
- function LoginPage() {
33
+ var LAST_EMAIL_KEY = "ampless.lastSignInEmail";
34
+ function LoginPage({ passkeysEnabled = true }) {
28
35
  const router = useRouter();
29
36
  const t = useT();
30
37
  const [mode, setMode] = useState("signIn");
@@ -34,6 +41,49 @@ function LoginPage() {
34
41
  const [error, setError] = useState(null);
35
42
  const [info, setInfo] = useState(null);
36
43
  const [loading, setLoading] = useState(false);
44
+ const [passkeySupported, setPasskeySupported] = useState(false);
45
+ const emailRef = useRef(null);
46
+ useEffect(() => {
47
+ setPasskeySupported(isWebAuthnSupported());
48
+ try {
49
+ const last = window.localStorage.getItem(LAST_EMAIL_KEY);
50
+ if (last) setEmail(last);
51
+ } catch {
52
+ }
53
+ }, []);
54
+ function rememberEmail(value) {
55
+ try {
56
+ window.localStorage.setItem(LAST_EMAIL_KEY, value);
57
+ } catch {
58
+ }
59
+ }
60
+ async function handlePasskeySignIn() {
61
+ setError(null);
62
+ setInfo(null);
63
+ if (!email) {
64
+ setError(t("auth.passkey.emailRequired"));
65
+ emailRef.current?.focus();
66
+ return;
67
+ }
68
+ setLoading(true);
69
+ try {
70
+ const outcome = await signInWithPasskey(email);
71
+ if (outcome.status === "signedIn") {
72
+ rememberEmail(email);
73
+ router.push("/admin");
74
+ router.refresh();
75
+ } else if (outcome.status === "noPasskey") {
76
+ setInfo(t("auth.passkey.noneRegistered"));
77
+ } else {
78
+ setError(t("auth.additionalStep", { step: outcome.step }));
79
+ }
80
+ } catch (err) {
81
+ console.error("[login-view] passkey sign-in failed", err);
82
+ setError(t(`auth.passkey.${classifyPasskeyError(err)}`));
83
+ } finally {
84
+ setLoading(false);
85
+ }
86
+ }
37
87
  function go(next) {
38
88
  setMode(next);
39
89
  setError(null);
@@ -50,6 +100,7 @@ function LoginPage() {
50
100
  if (mode === "signIn") {
51
101
  const result = await signIn({ username: email, password });
52
102
  if (result.isSignedIn) {
103
+ rememberEmail(email);
53
104
  router.push("/admin");
54
105
  router.refresh();
55
106
  } else {
@@ -109,6 +160,7 @@ function LoginPage() {
109
160
  Input,
110
161
  {
111
162
  id: "email",
163
+ ref: emailRef,
112
164
  type: "email",
113
165
  required: true,
114
166
  value: email,
@@ -149,6 +201,20 @@ function LoginPage() {
149
201
  info && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: info }),
150
202
  error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
151
203
  /* @__PURE__ */ jsx(Button, { type: "submit", className: "w-full", disabled: loading, children: loading ? t("auth.common.working") : t(`auth.${mode}.submit`) }),
204
+ mode === "signIn" && passkeysEnabled && passkeySupported && /* @__PURE__ */ jsxs(
205
+ Button,
206
+ {
207
+ type: "button",
208
+ variant: "outline",
209
+ className: "w-full gap-2",
210
+ disabled: loading,
211
+ onClick: () => void handlePasskeySignIn(),
212
+ children: [
213
+ /* @__PURE__ */ jsx(Fingerprint, { className: "h-4 w-4" }),
214
+ t("auth.signIn.passkeyButton")
215
+ ]
216
+ }
217
+ ),
152
218
  /* @__PURE__ */ jsxs("div", { className: "space-y-1 text-center text-sm", children: [
153
219
  mode === "signIn" && /* @__PURE__ */ jsxs(Fragment, { children: [
154
220
  /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx(
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  PostForm
4
- } from "./chunk-HANIMWMN.js";
4
+ } from "./chunk-4WYMEUYU.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-K44QF3MV.js";
7
+ } from "./chunk-4OFRX6QX.js";
8
8
 
9
9
  // src/components/edit-post-view.tsx
10
10
  import { useEffect, useState, use } from "react";
@@ -17,14 +17,20 @@ function EditPostPage({ params, previewEndpoint }) {
17
17
  const [post, setPost] = useState(null);
18
18
  const [loading, setLoading] = useState(true);
19
19
  const [missing, setMissing] = useState(false);
20
+ const [error, setError] = useState(false);
20
21
  useEffect(() => {
21
22
  getPostById(postId).then((p) => {
22
23
  if (!p) setMissing(true);
23
24
  else setPost(p);
25
+ }).catch((err) => {
26
+ console.error("[ampless admin] failed to load post for editing:", err);
27
+ setError(true);
24
28
  }).finally(() => setLoading(false));
25
29
  }, [postId]);
26
30
  if (loading)
27
31
  return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: t("common.loading") });
32
+ if (error)
33
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: t("common.loadError") });
28
34
  if (missing) notFound();
29
35
  return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-7xl p-4 md:p-8", children: [
30
36
  /* @__PURE__ */ jsx("h1", { className: "mb-6 text-2xl font-bold md:mb-8 md:text-3xl", children: t("posts.form.editTitle") }),
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  PostForm
4
- } from "./chunk-HANIMWMN.js";
4
+ } from "./chunk-4WYMEUYU.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-K44QF3MV.js";
7
+ } from "./chunk-4OFRX6QX.js";
8
8
 
9
9
  // src/components/new-post-view.tsx
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  MediaUploader
4
- } from "./chunk-SQ6LFPXH.js";
4
+ } from "./chunk-2TBGPB5H.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-K44QF3MV.js";
7
+ } from "./chunk-4OFRX6QX.js";
8
8
 
9
9
  // src/components/media-view.tsx
10
10
  import { jsx, jsxs } from "react/jsx-runtime";