@ampless/admin 1.0.0-beta.90 → 1.0.0-beta.92

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 (39) hide show
  1. package/dist/api/index.d.ts +1 -1
  2. package/dist/{chunk-F4BDO5XD.js → chunk-2Y5SRSCZ.js} +69 -3
  3. package/dist/{chunk-OS6QI7VW.js → chunk-3AQGYUVP.js} +1 -1
  4. package/dist/{chunk-OLI5JPHI.js → chunk-6275N7HG.js} +1 -1
  5. package/dist/{chunk-OVUT27GX.js → chunk-FVGWVH6Q.js} +19 -7
  6. package/dist/{chunk-LUONBBOA.js → chunk-FVHQJCH4.js} +2 -2
  7. package/dist/{chunk-VDNJ5KXR.js → chunk-HKMHNEFV.js} +1 -1
  8. package/dist/{chunk-TSHYVPGI.js → chunk-HRCUFIGW.js} +2 -2
  9. package/dist/{chunk-VX5FWOOG.js → chunk-IBFNFTJ6.js} +1 -1
  10. package/dist/{chunk-DIIJWZPF.js → chunk-M5QQJHM7.js} +60 -4
  11. package/dist/{chunk-K67HINPW.js → chunk-OAG4NI7Q.js} +1 -1
  12. package/dist/{chunk-STD3UYXK.js → chunk-OPHSYQQV.js} +2 -2
  13. package/dist/{chunk-T3R5QD5K.js → chunk-QO73C7VC.js} +1 -1
  14. package/dist/{chunk-DLIP4JOY.js → chunk-TEMSFG34.js} +2 -2
  15. package/dist/{chunk-25CIZ4R4.js → chunk-TMOFUAU7.js} +1 -1
  16. package/dist/{chunk-X4V3LAP5.js → chunk-UUZAKCZZ.js} +2 -2
  17. package/dist/chunk-VJ5SDYVW.js +172 -0
  18. package/dist/chunk-ZDIDJCSU.js +40 -0
  19. package/dist/components/account-view.d.ts +8 -0
  20. package/dist/components/account-view.js +10 -0
  21. package/dist/components/admin-dashboard.js +3 -3
  22. package/dist/components/edit-post-view.js +5 -5
  23. package/dist/components/index.d.ts +1 -1
  24. package/dist/components/index.js +7 -7
  25. package/dist/components/login-view.d.ts +3 -1
  26. package/dist/components/login-view.js +4 -3
  27. package/dist/components/mcp-tokens-view.js +3 -3
  28. package/dist/components/media-view.js +5 -5
  29. package/dist/components/new-post-view.js +5 -5
  30. package/dist/components/plugin-settings-form.js +3 -3
  31. package/dist/components/posts-list-view.js +3 -3
  32. package/dist/components/users-list-view.js +3 -3
  33. package/dist/{i18n-BA6o0jvS.d.ts → i18n-CecqYBeP.d.ts} +88 -3
  34. package/dist/index.d.ts +2 -2
  35. package/dist/index.js +1 -1
  36. package/dist/metafile-esm.json +1 -1
  37. package/dist/pages/index.d.ts +21 -4
  38. package/dist/pages/index.js +45 -23
  39. package/package.json +2 -2
@@ -2,7 +2,7 @@ import { NextRequest } from 'next/server';
2
2
  import { Admin } from '../index.js';
3
3
  import 'ampless';
4
4
  import '@ampless/runtime';
5
- import '../i18n-BA6o0jvS.js';
5
+ import '../i18n-CecqYBeP.js';
6
6
  import '@aws-amplify/adapter-nextjs';
7
7
 
8
8
  /**
@@ -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-VX5FWOOG.js";
9
+ } from "./chunk-IBFNFTJ6.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,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-VX5FWOOG.js";
4
+ } from "./chunk-IBFNFTJ6.js";
5
5
 
6
6
  // src/components/admin-dashboard.tsx
7
7
  import { useEffect, useState } from "react";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-2ITWLRYF.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-VX5FWOOG.js";
7
+ } from "./chunk-IBFNFTJ6.js";
8
8
 
9
9
  // src/lib/upload.ts
10
10
  import { uploadData } from "aws-amplify/storage";
@@ -1,23 +1,23 @@
1
1
  'use client';
2
+ import {
3
+ setMcpTokenStore
4
+ } from "./chunk-C7G5AQ3L.js";
2
5
  import {
3
6
  invalidateSiteSettingsCache
4
7
  } from "./chunk-D72XF3Q3.js";
5
8
  import {
6
9
  clearAllDrafts
7
- } from "./chunk-X4V3LAP5.js";
10
+ } from "./chunk-UUZAKCZZ.js";
8
11
  import {
9
12
  setAdminCmsConfigClient
10
- } from "./chunk-OLI5JPHI.js";
13
+ } from "./chunk-6275N7HG.js";
11
14
  import {
12
15
  setAdminMediaContext
13
16
  } from "./chunk-2ITWLRYF.js";
14
- import {
15
- setMcpTokenStore
16
- } from "./chunk-C7G5AQ3L.js";
17
17
  import {
18
18
  useLocale,
19
19
  useT
20
- } from "./chunk-VX5FWOOG.js";
20
+ } from "./chunk-IBFNFTJ6.js";
21
21
 
22
22
  // src/lib/amplify-client.ts
23
23
  import { Amplify } from "aws-amplify";
@@ -342,6 +342,7 @@ import {
342
342
  Puzzle,
343
343
  LogOut,
344
344
  ExternalLink,
345
+ UserCircle,
345
346
  Menu,
346
347
  X
347
348
  } from "lucide-react";
@@ -466,7 +467,18 @@ function Sidebar({
466
467
  ]
467
468
  }
468
469
  ),
469
- /* @__PURE__ */ jsx2("div", { className: "px-3 py-2 text-xs text-muted-foreground truncate", children: email }),
470
+ /* @__PURE__ */ jsxs(
471
+ Link,
472
+ {
473
+ href: "/admin/account",
474
+ className: "flex items-center gap-3 rounded-md px-3 py-2 text-xs text-muted-foreground hover:bg-accent hover:text-accent-foreground",
475
+ title: t("sidebar.account"),
476
+ children: [
477
+ /* @__PURE__ */ jsx2(UserCircle, { className: "h-4 w-4 shrink-0" }),
478
+ /* @__PURE__ */ jsx2("span", { className: "truncate", children: email })
479
+ ]
480
+ }
481
+ ),
470
482
  /* @__PURE__ */ jsxs(
471
483
  Button,
472
484
  {
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  MediaUploader
4
- } from "./chunk-DLIP4JOY.js";
4
+ } from "./chunk-TEMSFG34.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-VX5FWOOG.js";
7
+ } from "./chunk-IBFNFTJ6.js";
8
8
 
9
9
  // src/components/media-view.tsx
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-C7G5AQ3L.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-VX5FWOOG.js";
7
+ } from "./chunk-IBFNFTJ6.js";
8
8
 
9
9
  // src/components/mcp-tokens-view.tsx
10
10
  import { useEffect, useState } from "react";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  PostForm
4
- } from "./chunk-X4V3LAP5.js";
4
+ } from "./chunk-UUZAKCZZ.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-VX5FWOOG.js";
7
+ } from "./chunk-IBFNFTJ6.js";
8
8
 
9
9
  // src/components/edit-post-view.tsx
10
10
  import { useEffect, useState, use } from "react";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  translate
4
- } from "./chunk-DIIJWZPF.js";
4
+ } from "./chunk-M5QQJHM7.js";
5
5
 
6
6
  // src/components/i18n-provider.tsx
7
7
  import { createContext, useContext, useMemo } from "react";
@@ -41,7 +41,8 @@ var en_default = {
41
41
  signOut: "Sign out",
42
42
  site: "Site",
43
43
  openMenu: "Open menu",
44
- closeMenu: "Close menu"
44
+ closeMenu: "Close menu",
45
+ account: "Account"
45
46
  },
46
47
  dashboard: {
47
48
  title: "Dashboard",
@@ -293,7 +294,8 @@ var en_default = {
293
294
  description: "Sign in to manage your site.",
294
295
  submit: "Sign in",
295
296
  forgotPassword: "Forgot password?",
296
- createAccount: "Create admin account"
297
+ createAccount: "Create admin account",
298
+ passkeyButton: "Sign in with passkey"
297
299
  },
298
300
  signUp: {
299
301
  title: "Create admin account",
@@ -324,6 +326,14 @@ var en_default = {
324
326
  invalidCredentials: "Invalid email or password.",
325
327
  passwordTooShort: "Password must be at least 8 characters.",
326
328
  userExists: "An account with this email already exists."
329
+ },
330
+ passkey: {
331
+ emailRequired: "Enter your email first, then sign in with your passkey.",
332
+ noneRegistered: "No passkey is registered for this account yet. Sign in with your password, then add one from the account page.",
333
+ cancelled: "Passkey sign-in was cancelled.",
334
+ unsupported: "This browser does not support passkeys.",
335
+ 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.",
336
+ failed: "Passkey sign-in failed. Try again, or sign in with your password."
327
337
  }
328
338
  },
329
339
  plugins: {
@@ -385,6 +395,24 @@ var en_default = {
385
395
  tagLabel: "Tag",
386
396
  permalink: "Permalink",
387
397
  shareOnX: "Share on X"
398
+ },
399
+ account: {
400
+ title: "Account",
401
+ passkeys: {
402
+ title: "Passkeys",
403
+ add: "Add passkey",
404
+ adding: "Adding...",
405
+ added: "Passkey added.",
406
+ loading: "Loading passkeys...",
407
+ empty: "No passkeys yet. Add one to sign in with Face ID, Touch ID, or your security key.",
408
+ columnName: "Name",
409
+ columnCreated: "Created",
410
+ delete: "Delete",
411
+ deleteConfirm: "Delete this passkey? You will no longer be able to sign in with it.",
412
+ loadError: "Failed to load passkeys",
413
+ unsupported: "This browser does not support passkeys.",
414
+ disabled: "Passkeys are disabled for this site."
415
+ }
388
416
  }
389
417
  };
390
418
 
@@ -431,7 +459,8 @@ var ja_default = {
431
459
  signOut: "\u30B5\u30A4\u30F3\u30A2\u30A6\u30C8",
432
460
  site: "\u30B5\u30A4\u30C8",
433
461
  openMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u958B\u304F",
434
- closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B"
462
+ closeMenu: "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B",
463
+ account: "\u30A2\u30AB\u30A6\u30F3\u30C8"
435
464
  },
436
465
  dashboard: {
437
466
  title: "\u30C0\u30C3\u30B7\u30E5\u30DC\u30FC\u30C9",
@@ -683,7 +712,8 @@ var ja_default = {
683
712
  description: "\u30B5\u30A4\u30F3\u30A4\u30F3\u3057\u3066\u30B5\u30A4\u30C8\u3092\u7BA1\u7406\u3057\u307E\u3059\u3002",
684
713
  submit: "\u30B5\u30A4\u30F3\u30A4\u30F3",
685
714
  forgotPassword: "\u30D1\u30B9\u30EF\u30FC\u30C9\u3092\u304A\u5FD8\u308C\u3067\u3059\u304B?",
686
- createAccount: "\u7BA1\u7406\u8005\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210"
715
+ createAccount: "\u7BA1\u7406\u8005\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210",
716
+ passkeyButton: "\u30D1\u30B9\u30AD\u30FC\u3067\u30B5\u30A4\u30F3\u30A4\u30F3"
687
717
  },
688
718
  signUp: {
689
719
  title: "\u7BA1\u7406\u8005\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u4F5C\u6210",
@@ -714,6 +744,14 @@ var ja_default = {
714
744
  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",
715
745
  passwordTooShort: "\u30D1\u30B9\u30EF\u30FC\u30C9\u306F8\u6587\u5B57\u4EE5\u4E0A\u5FC5\u8981\u3067\u3059\u3002",
716
746
  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"
747
+ },
748
+ passkey: {
749
+ 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",
750
+ 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",
751
+ cancelled: "\u30D1\u30B9\u30AD\u30FC\u306E\u30B5\u30A4\u30F3\u30A4\u30F3\u304C\u30AD\u30E3\u30F3\u30BB\u30EB\u3055\u308C\u307E\u3057\u305F\u3002",
752
+ unsupported: "\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306F\u30D1\u30B9\u30AD\u30FC\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
753
+ 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",
754
+ 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"
717
755
  }
718
756
  },
719
757
  plugins: {
@@ -775,6 +813,24 @@ var ja_default = {
775
813
  tagLabel: "\u30BF\u30B0",
776
814
  permalink: "\u30D1\u30FC\u30DE\u30EA\u30F3\u30AF",
777
815
  shareOnX: "X\u3067\u5171\u6709"
816
+ },
817
+ account: {
818
+ title: "\u30A2\u30AB\u30A6\u30F3\u30C8",
819
+ passkeys: {
820
+ title: "\u30D1\u30B9\u30AD\u30FC",
821
+ add: "\u30D1\u30B9\u30AD\u30FC\u3092\u8FFD\u52A0",
822
+ adding: "\u8FFD\u52A0\u4E2D...",
823
+ added: "\u30D1\u30B9\u30AD\u30FC\u3092\u8FFD\u52A0\u3057\u307E\u3057\u305F\u3002",
824
+ loading: "\u30D1\u30B9\u30AD\u30FC\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D...",
825
+ 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",
826
+ columnName: "\u540D\u524D",
827
+ columnCreated: "\u4F5C\u6210\u65E5",
828
+ delete: "\u524A\u9664",
829
+ 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",
830
+ loadError: "\u30D1\u30B9\u30AD\u30FC\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
831
+ unsupported: "\u3053\u306E\u30D6\u30E9\u30A6\u30B6\u306F\u30D1\u30B9\u30AD\u30FC\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\u3002",
832
+ disabled: "\u3053\u306E\u30B5\u30A4\u30C8\u3067\u306F\u30D1\u30B9\u30AD\u30FC\u306F\u7121\u52B9\u3067\u3059\u3002"
833
+ }
778
834
  }
779
835
  };
780
836
 
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-VX5FWOOG.js";
4
+ } from "./chunk-IBFNFTJ6.js";
5
5
 
6
6
  // src/components/users-list-view.tsx
7
7
  import { useEffect, useState } from "react";
@@ -1,10 +1,10 @@
1
1
  'use client';
2
2
  import {
3
3
  PostForm
4
- } from "./chunk-X4V3LAP5.js";
4
+ } from "./chunk-UUZAKCZZ.js";
5
5
  import {
6
6
  useT
7
- } from "./chunk-VX5FWOOG.js";
7
+ } from "./chunk-IBFNFTJ6.js";
8
8
 
9
9
  // src/components/new-post-view.tsx
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  useLocale,
7
7
  useT
8
- } from "./chunk-VX5FWOOG.js";
8
+ } from "./chunk-IBFNFTJ6.js";
9
9
 
10
10
  // src/components/plugin-settings-form.tsx
11
11
  import { useState as useState2, useEffect as useEffect2 } from "react";
@@ -3,13 +3,13 @@ import {
3
3
  ImageUploadDialog,
4
4
  createMediaRow,
5
5
  getMediaProcessingDefaults
6
- } from "./chunk-OLI5JPHI.js";
6
+ } from "./chunk-6275N7HG.js";
7
7
  import {
8
8
  publicMediaUrl
9
9
  } from "./chunk-2ITWLRYF.js";
10
10
  import {
11
11
  useT
12
- } from "./chunk-VX5FWOOG.js";
12
+ } from "./chunk-IBFNFTJ6.js";
13
13
 
14
14
  // src/components/media-uploader.tsx
15
15
  import { useState, useEffect, useCallback, useRef } from "react";
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import {
3
3
  useT
4
- } from "./chunk-VX5FWOOG.js";
4
+ } from "./chunk-IBFNFTJ6.js";
5
5
 
6
6
  // src/components/posts-list-view.tsx
7
7
  import { useEffect, useMemo, useState } from "react";
@@ -4,7 +4,7 @@ import {
4
4
  getAdminCmsConfig,
5
5
  getMediaProcessingDefaults,
6
6
  uploadProcessedImage
7
- } from "./chunk-OLI5JPHI.js";
7
+ } from "./chunk-6275N7HG.js";
8
8
  import {
9
9
  getAdminEditorExtensions,
10
10
  getAdminTiptapNodeHtml,
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-2ITWLRYF.js";
16
16
  import {
17
17
  useT
18
- } from "./chunk-VX5FWOOG.js";
18
+ } from "./chunk-IBFNFTJ6.js";
19
19
 
20
20
  // src/components/media-picker.tsx
21
21
  import { useEffect, useRef, useState } from "react";
@@ -0,0 +1,172 @@
1
+ 'use client';
2
+ import {
3
+ isWebAuthnSupported
4
+ } from "./chunk-ZDIDJCSU.js";
5
+ import {
6
+ useT
7
+ } from "./chunk-IBFNFTJ6.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
+ };
@@ -0,0 +1,40 @@
1
+ // src/lib/passkey.ts
2
+ import { signIn } from "aws-amplify/auth";
3
+ var FIRST_FACTOR_SELECTION = "CONTINUE_SIGN_IN_WITH_FIRST_FACTOR_SELECTION";
4
+ function isWebAuthnSupported() {
5
+ return typeof window !== "undefined" && typeof window.PublicKeyCredential !== "undefined" && typeof navigator !== "undefined" && typeof navigator.credentials !== "undefined" && typeof navigator.credentials.get === "function";
6
+ }
7
+ function classifyPasskeyError(err) {
8
+ const name = typeof err === "object" && err !== null && "name" in err ? String(err.name) : "";
9
+ if (name === "NotAllowedError") return "cancelled";
10
+ if (name === "SecurityError") return "rpMismatch";
11
+ return "failed";
12
+ }
13
+ function interpretPasskeySignInResult(result) {
14
+ if (result.isSignedIn) return { status: "signedIn" };
15
+ if (result.nextStep.signInStep === FIRST_FACTOR_SELECTION) {
16
+ return { status: "noPasskey" };
17
+ }
18
+ return { status: "otherStep", step: result.nextStep.signInStep };
19
+ }
20
+ async function signInWithPasskey(username) {
21
+ const result = await signIn({
22
+ username,
23
+ options: {
24
+ authFlowType: "USER_AUTH",
25
+ preferredChallenge: "WEB_AUTHN"
26
+ }
27
+ });
28
+ return interpretPasskeySignInResult(result);
29
+ }
30
+ function isWebAuthnEnabled(outputs) {
31
+ const narrowed = outputs;
32
+ return narrowed.auth?.passwordless?.web_authn != null;
33
+ }
34
+
35
+ export {
36
+ isWebAuthnSupported,
37
+ classifyPasskeyError,
38
+ signInWithPasskey,
39
+ isWebAuthnEnabled
40
+ };
@@ -0,0 +1,8 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ declare function AccountView({ currentUserEmail, passkeysEnabled }: {
4
+ currentUserEmail: string;
5
+ passkeysEnabled: boolean;
6
+ }): react_jsx_runtime.JSX.Element;
7
+
8
+ export { AccountView };