@undefineds.co/xpod 0.4.19 → 0.4.20

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 (136) hide show
  1. package/dist/api/container/common.js +2 -1
  2. package/dist/api/handlers/AiConfigHandler.js +10 -2
  3. package/dist/runtime/host/node/NodeRuntimeHost.js +7 -3
  4. package/dist/runtime/port-finder.d.ts +0 -5
  5. package/dist/runtime/port-finder.js +24 -12
  6. package/node_modules/@undefineds.co/extension-sdk/dist/react/solid-auth-boundary.d.ts +20 -2
  7. package/node_modules/@undefineds.co/extension-sdk/dist/react/solid-auth-boundary.js +142 -125
  8. package/node_modules/@undefineds.co/shared-ui/dist/focus.d.ts +1 -1
  9. package/node_modules/@undefineds.co/shared-ui/dist/focus.js +1 -1
  10. package/node_modules/@undefineds.co/shared-ui/dist/index.d.ts +1 -0
  11. package/node_modules/@undefineds.co/shared-ui/dist/index.js +1 -0
  12. package/node_modules/@undefineds.co/shared-ui/dist/login/LoginModal.d.ts +2 -0
  13. package/node_modules/@undefineds.co/shared-ui/dist/login/LoginModal.js +2 -0
  14. package/node_modules/@undefineds.co/shared-ui/dist/login/LoginView.d.ts +3 -0
  15. package/node_modules/@undefineds.co/shared-ui/dist/login/LoginView.js +3 -0
  16. package/node_modules/@undefineds.co/shared-ui/dist/login.d.ts +15 -0
  17. package/node_modules/@undefineds.co/shared-ui/dist/login.js +15 -0
  18. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/AccountSections.d.ts +141 -0
  19. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/AccountSections.js +67 -0
  20. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/ConsentView.d.ts +53 -0
  21. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/ConsentView.js +27 -0
  22. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/DeviceDialogs.d.ts +40 -0
  23. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/DeviceDialogs.js +33 -0
  24. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.d.ts +15 -0
  25. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpChrome.js +8 -0
  26. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpViews.d.ts +93 -0
  27. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/IdpViews.js +88 -0
  28. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/NetworkPanel.d.ts +46 -0
  29. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/NetworkPanel.js +42 -0
  30. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignIn.d.ts +63 -0
  31. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignIn.js +65 -0
  32. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.d.ts +31 -0
  33. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/PodSignInFrame.js +111 -0
  34. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/StorageBadge.d.ts +8 -0
  35. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/StorageBadge.js +7 -0
  36. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/XpodMark.d.ts +8 -0
  37. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/XpodMark.js +8 -0
  38. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/account-parts.d.ts +37 -0
  39. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/account-parts.js +33 -0
  40. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/copy.d.ts +156 -0
  41. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/copy.js +304 -0
  42. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/index.d.ts +20 -0
  43. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/index.js +18 -0
  44. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.d.ts +69 -0
  45. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/parts.js +65 -0
  46. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/types.d.ts +14 -0
  47. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/types.js +1 -0
  48. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/webid-name.d.ts +5 -0
  49. package/node_modules/@undefineds.co/shared-ui/dist/pod-sign-in/webid-name.js +14 -0
  50. package/node_modules/@undefineds.co/shared-ui/dist/storage-bootstrap.d.ts +3 -0
  51. package/node_modules/@undefineds.co/shared-ui/dist/storage-bootstrap.js +3 -0
  52. package/node_modules/@undefineds.co/shared-ui/dist/theme.css +168 -83
  53. package/node_modules/@undefineds.co/shared-ui/dist/webid-auth.d.ts +5 -0
  54. package/node_modules/@undefineds.co/shared-ui/dist/webid-auth.js +5 -0
  55. package/package.json +2 -2
  56. package/static/app/assets/main.css +1 -1
  57. package/static/app/assets/main.js +55 -15
  58. package/static/dashboard/assets/{AiConfigContext-B5QP7ow2.js → AiConfigContext-CZEW16so.js} +1 -1
  59. package/static/dashboard/assets/{AiConfigPage-C_TdlUjq.js → AiConfigPage-Cfp0fRX1.js} +1 -1
  60. package/static/dashboard/assets/{DocumentProcessingPanel-CormNOdI.js → DocumentProcessingPanel-DaaRCLp6.js} +1 -1
  61. package/static/dashboard/assets/{IndexLifecyclePanel-S9wNNyHO.js → IndexLifecyclePanel-BCz3jNM8.js} +1 -1
  62. package/static/dashboard/assets/{IndexSubjectPanel-CofZT1oE.js → IndexSubjectPanel-DVCVIt7V.js} +1 -1
  63. package/static/dashboard/assets/ModelAssignmentsPanel-DxauxtEY.js +1 -0
  64. package/static/dashboard/assets/{ModelsPage-DZ46VnUh.js → ModelsPage-B8F3AtZL.js} +3 -3
  65. package/static/dashboard/assets/{NetworkPage-TwbIWASf.js → NetworkPage-Cu7ty71x.js} +2 -2
  66. package/static/dashboard/assets/{PaneListHeader-BtVnyutC.js → PaneListHeader-BEAXSlD9.js} +1 -1
  67. package/static/dashboard/assets/{SearchIndexingPanel-BCq_YMOP.js → SearchIndexingPanel-DEr1EDDC.js} +1 -1
  68. package/static/dashboard/assets/{StatusSubjectPanel-DsUKTysU.js → StatusSubjectPanel-Cg92MGhO.js} +1 -1
  69. package/static/dashboard/assets/{StatusWorkspace-CaVJBRqI.js → StatusWorkspace-BmJSOF3z.js} +1 -1
  70. package/static/dashboard/assets/{SystemSettingsPage-DRWfPjtu.js → SystemSettingsPage-rziu4Q3U.js} +1 -1
  71. package/static/dashboard/assets/SystemSettingsSubjectPanel-D16DGDNW.js +33 -0
  72. package/static/dashboard/assets/{XpodAiConnectionsPodStore-C9tHqutw.js → XpodAiConnectionsPodStore-FlZ0LEn0.js} +1 -1
  73. package/static/dashboard/assets/{activity-tgjewO88.js → activity-D55X-5L5.js} +1 -1
  74. package/static/dashboard/assets/{badge-Czun47II.js → badge-CRf1ISRJ.js} +1 -1
  75. package/static/dashboard/assets/{collection-runtime-DnYlU23-.js → collection-runtime-CfXMuwNc.js} +1 -1
  76. package/static/dashboard/assets/dashboard-B3kGI946.css +1 -0
  77. package/static/dashboard/assets/{dashboard-CF8SbVSR.js → dashboard-D4DWPaAy.js} +122 -122
  78. package/static/dashboard/assets/{dialog-D-Yc_lg-.js → dialog-EVyIyybL.js} +1 -1
  79. package/static/dashboard/assets/{download-vFuOkjCi.js → download-Et4aRDT2.js} +1 -1
  80. package/static/dashboard/assets/{external-link-Cg_VIecD.js → external-link-DT746o7t.js} +1 -1
  81. package/static/dashboard/assets/{index-IXCNeEwi.js → index-D2agcVRx.js} +3 -3
  82. package/static/dashboard/assets/{index-0au0WwkE.js → index-_wC13YGI.js} +1 -1
  83. package/static/dashboard/assets/{index-YDcVky3o.js → index-z6TnJzW6.js} +1 -1
  84. package/static/dashboard/assets/{key-round-9n1xgLbG.js → key-round-CHnogQ1D.js} +1 -1
  85. package/static/dashboard/assets/{rotate-ccw-DJ6s86DW.js → rotate-ccw-BNVDC4Zr.js} +1 -1
  86. package/static/dashboard/assets/{skeleton-Dg9HePtI.js → skeleton-BzsZekVf.js} +1 -1
  87. package/static/dashboard/assets/{waypoints-uxc_qOzk.js → waypoints-CLZA22UN.js} +1 -1
  88. package/static/dashboard/assets/{workspace-layout-BNtAXhPW.js → workspace-layout-urYcwAhS.js} +1 -1
  89. package/static/dashboard/dashboard.html +2 -2
  90. package/static/settings/assets/{AiConfigContext-y6ha7pGs.js → AiConfigContext-B2cVJFNu.js} +1 -1
  91. package/static/settings/assets/AiConfigPage-D_5jSPPF.js +1 -0
  92. package/static/settings/assets/{DocumentProcessingPanel-Oa_cxMcn.js → DocumentProcessingPanel-DodGlYIk.js} +1 -1
  93. package/static/settings/assets/{IndexLifecyclePanel-DlrnI0Kh.js → IndexLifecyclePanel-BtH-0GKv.js} +1 -1
  94. package/static/settings/assets/{IndexSubjectPanel-Dhsl0W32.js → IndexSubjectPanel-CqKhUcw2.js} +1 -1
  95. package/static/settings/assets/ModelAssignmentsPanel-Cam9V9BN.js +1 -0
  96. package/static/settings/assets/{ModelsPage-CuNW1hGX.js → ModelsPage-DWFic09l.js} +3 -3
  97. package/static/settings/assets/{NetworkPage-BBD2Lc_P.js → NetworkPage-CSwHoAWm.js} +2 -2
  98. package/static/settings/assets/{PaneListHeader-C81adSBX.js → PaneListHeader-270Z179Z.js} +1 -1
  99. package/static/settings/assets/{SearchIndexingPanel-Dc4QKWd0.js → SearchIndexingPanel-ATHKMDBQ.js} +1 -1
  100. package/static/settings/assets/{StatusSubjectPanel-DuxOvqzS.js → StatusSubjectPanel-DDWo3v3d.js} +1 -1
  101. package/static/settings/assets/StatusWorkspace-BYgqeCBM.js +1 -0
  102. package/static/settings/assets/SystemSettingsPage-BRaQAFWC.js +1 -0
  103. package/static/settings/assets/SystemSettingsSubjectPanel-Dc0roh9d.js +33 -0
  104. package/static/settings/assets/{XpodAiConnectionsPodStore-G0ClZksP.js → XpodAiConnectionsPodStore-OndzbigX.js} +2 -2
  105. package/static/settings/assets/{activity-DT8pzOea.js → activity-BAZEToJ3.js} +1 -1
  106. package/static/settings/assets/auth-callback-Bd3tvCOa.js +1 -0
  107. package/static/settings/assets/{badge-CvKRjSY2.js → badge-Cpol0LWF.js} +1 -1
  108. package/static/settings/assets/{collection-runtime-C6ZAp2cM.js → collection-runtime-B5vGx3gk.js} +1 -1
  109. package/static/settings/assets/{dialog-LFluK7Nx.js → dialog-_7AVvv5q.js} +1 -1
  110. package/static/settings/assets/{download-CZJeqUm1.js → download-DRAi-dnn.js} +1 -1
  111. package/static/settings/assets/{external-link-BsDft3Oe.js → external-link--Yq8inDb.js} +1 -1
  112. package/static/settings/assets/global-B3kGI946.css +1 -0
  113. package/static/settings/assets/{global-cmKEraT4.js → global-DMtP8ZF4.js} +111 -111
  114. package/static/settings/assets/{index-BiqSFOS1.js → index-C36H49PZ.js} +1 -1
  115. package/static/settings/assets/{index-XRgmPBKw.js → index-CQR3iePj.js} +1 -1
  116. package/static/settings/assets/{index-BMPlaRWG.js → index-DURnCfoQ.js} +1 -1
  117. package/static/settings/assets/{index-browser-B0slhJKc.js → index-browser-kAl2JmYB.js} +1 -1
  118. package/static/settings/assets/{key-round-D1XsWbN_.js → key-round-DzpWBVN9.js} +1 -1
  119. package/static/settings/assets/{rotate-ccw-CEe0P0fY.js → rotate-ccw-CtVsCcAx.js} +1 -1
  120. package/static/settings/assets/settings-DQtt6lql.js +1 -0
  121. package/static/settings/assets/{skeleton-DA404w6m.js → skeleton-R_ACks7d.js} +1 -1
  122. package/static/settings/assets/{waypoints-DJDWc4S9.js → waypoints-D-Qv_sME.js} +1 -1
  123. package/static/settings/assets/{workspace-layout-K38jT4OV.js → workspace-layout-BSMCQq35.js} +1 -1
  124. package/static/settings/auth-callback.html +3 -3
  125. package/static/settings/settings.html +3 -3
  126. package/static/dashboard/assets/ModelAssignmentsPanel-9tgOjj-y.js +0 -1
  127. package/static/dashboard/assets/SystemSettingsSubjectPanel-CgDP7Ii2.js +0 -33
  128. package/static/dashboard/assets/dashboard-DvuM3VTP.css +0 -1
  129. package/static/settings/assets/AiConfigPage-CIcIWdXA.js +0 -1
  130. package/static/settings/assets/ModelAssignmentsPanel-CHoiRZeL.js +0 -1
  131. package/static/settings/assets/StatusWorkspace-DeD3zi3G.js +0 -1
  132. package/static/settings/assets/SystemSettingsPage-8sHOaPb8.js +0 -1
  133. package/static/settings/assets/SystemSettingsSubjectPanel-B1JXut7S.js +0 -33
  134. package/static/settings/assets/auth-callback-1BlsOtMG.js +0 -1
  135. package/static/settings/assets/global-DvuM3VTP.css +0 -1
  136. package/static/settings/assets/settings-DfjkqEJ8.js +0 -1
@@ -44,22 +44,34 @@ export function LoginAvatar({ name, avatarUrl, size = 'lg', marker, }) {
44
44
  const sizeClassName = size === 'lg' ? 'h-16 w-16 text-xl' : 'h-10 w-10 text-sm';
45
45
  return (_jsxs("div", { className: "relative shrink-0", children: [avatarUrl ? (_jsx("img", { src: avatarUrl, alt: name, className: cn(sizeClassName, 'rounded-[18%] border border-border/50 object-cover shadow-sm') })) : (_jsx("div", { "aria-hidden": "true", className: cn(sizeClassName, 'flex items-center justify-center rounded-[18%] bg-primary/10 font-semibold text-primary shadow-sm'), children: name.slice(0, 1).toUpperCase() })), marker ? (_jsx("div", { className: "absolute -bottom-1 -right-1", children: marker })) : null] }));
46
46
  }
47
+ /**
48
+ * @deprecated Use PodSignIn (state `restoring`) (see `pod-sign-in/`). Kept for one more version; behavior is unchanged.
49
+ */
47
50
  export function LoginRestoringView({ accountName, avatarUrl, label, }) {
48
51
  const visibleLabel = legacyLoginLabel(label, LEGACY_LOGIN_RESTORING_DEFAULT_COPY.label);
49
52
  return (_jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 p-6", role: "status", children: [accountName ? (_jsxs(_Fragment, { children: [_jsx(LoginAvatar, { name: accountName, avatarUrl: avatarUrl, size: "lg" }), _jsx("p", { className: "text-sm font-medium text-foreground", children: accountName })] })) : null, _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground", "aria-hidden": "true" }), _jsx("p", { className: "text-xs text-muted-foreground", children: visibleLabel })] }));
50
53
  }
54
+ /**
55
+ * @deprecated Use PodSignIn (`busy` on the primary action) (see `pod-sign-in/`). Kept for one more version; behavior is unchanged.
56
+ */
51
57
  export function LoginConnectingView({ title, detail, providerLabel, providerHost, onCancel, cancelLabel, }) {
52
58
  const visibleTitle = legacyLoginLabel(title, LEGACY_LOGIN_CONNECTING_DEFAULT_COPY.title);
53
59
  const visibleDetail = legacyLoginLabel(detail, LEGACY_LOGIN_CONNECTING_DEFAULT_COPY.detail);
54
60
  const visibleCancelLabel = legacyLoginLabel(cancelLabel, LEGACY_LOGIN_CONNECTING_DEFAULT_COPY.cancelLabel);
55
61
  return (_jsxs("div", { className: "flex h-full flex-1 flex-col", children: [_jsxs("div", { className: "flex flex-1 flex-col items-center justify-center px-6 py-8 text-center", role: "status", children: [_jsx(Loader2, { className: "mb-4 h-8 w-8 animate-spin text-primary", "aria-hidden": "true" }), _jsx("p", { className: "text-sm font-medium text-foreground", children: visibleTitle }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: visibleDetail }), providerLabel || providerHost ? (_jsxs("div", { className: "mt-4 w-full max-w-[18rem] rounded-lg border border-border/60 bg-muted/30 px-3 py-2", children: [providerLabel ? (_jsx("p", { className: "truncate text-xs font-medium text-foreground", children: providerLabel })) : null, providerHost ? (_jsx("p", { className: "mt-0.5 truncate text-[11px] text-muted-foreground", children: providerHost })) : null] })) : null] }), onCancel ? (_jsx("div", { className: "shrink-0 px-5 pb-5", children: _jsx("button", { type: "button", onClick: onCancel, className: cn('h-9 w-full cursor-pointer rounded-md text-xs text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground', interactiveFocusClass), children: visibleCancelLabel }) })) : null] }));
56
62
  }
63
+ /**
64
+ * @deprecated Use PodSignIn (states `remembered` / `expired`) (see `pod-sign-in/`). Kept for one more version; behavior is unchanged.
65
+ */
57
66
  export function LoginAccountView({ name, avatarUrl, bindingLabel, expired = false, expiredTitle, expiredDescription, enterLabel, switchLabel, error, onDismissError, dismissErrorLabel, onEnter, onSwitchAccount, onReauthenticate, }) {
58
67
  const visibleExpiredTitle = legacyLoginLabel(expiredTitle, LEGACY_LOGIN_ACCOUNT_DEFAULT_COPY.expiredTitle);
59
68
  const visibleEnterLabel = legacyLoginLabel(enterLabel, LEGACY_LOGIN_ACCOUNT_DEFAULT_COPY.enterLabel);
60
69
  const visibleSwitchLabel = legacyLoginLabel(switchLabel, LEGACY_LOGIN_ACCOUNT_DEFAULT_COPY.switchLabel);
61
70
  return (_jsxs("div", { className: "flex h-full flex-1 flex-col", children: [_jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-4 px-5 py-8", children: [expired ? (_jsxs("div", { className: "inline-flex items-center gap-1.5 rounded-full border border-primary/20 bg-primary/5 px-2.5 py-1 text-[11px] font-medium text-primary", children: [_jsx(AlertCircle, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), visibleExpiredTitle] })) : null, _jsx(LoginAvatar, { name: name, avatarUrl: avatarUrl, size: "lg" }), _jsxs("div", { className: "space-y-1 text-center", children: [_jsx("p", { className: "text-base font-semibold text-foreground", children: name }), bindingLabel ? _jsx("p", { className: "text-xs text-muted-foreground", children: bindingLabel }) : null] }), expired && expiredDescription ? (_jsx("p", { className: "max-w-[18rem] text-center text-xs leading-5 text-muted-foreground", children: expiredDescription })) : null] }), !expired ? (_jsx(LoginErrorBanner, { error: error, onDismiss: onDismissError, dismissLabel: dismissErrorLabel })) : null, _jsxs("div", { className: "shrink-0 space-y-2 px-5 pb-5 pt-2", children: [_jsx("button", { type: "button", onClick: expired && onReauthenticate ? onReauthenticate : onEnter, className: cn('h-10 w-full cursor-pointer rounded-xl bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:bg-primary/80', buttonFocusClass), children: visibleEnterLabel }), onSwitchAccount ? (_jsx("button", { type: "button", onClick: onSwitchAccount, className: cn('h-9 w-full cursor-pointer rounded-lg text-xs text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground focus-visible:bg-muted/40 focus-visible:text-foreground', buttonFocusClass), children: visibleSwitchLabel })) : null] })] }));
62
71
  }
72
+ /**
73
+ * @deprecated Use PodSignIn (states `remembered` / `expired`) (see `pod-sign-in/`). Kept for one more version; behavior is unchanged.
74
+ */
63
75
  export function RememberedLoginView({ state, copy, onContinue, onReauthenticate, onSwitchAccount, onCancel, onDismissError, }) {
64
76
  if (state.status === 'authenticated')
65
77
  return null;
@@ -78,6 +90,9 @@ export function RememberedLoginView({ state, copy, onContinue, onReauthenticate,
78
90
  ? copy.continueLabel(remembered.displayName)
79
91
  : copy.reauthenticateLabel(remembered.displayName), switchLabel: copy.switchAccountLabel, error: state.status === 'error' && !expired ? state.message : undefined, onDismissError: onDismissError, onEnter: canContinue ? onContinue : onReauthenticate, onSwitchAccount: onSwitchAccount }));
80
92
  }
93
+ /**
94
+ * @deprecated Use PodSignIn with a `notice` (see `pod-sign-in/`). Kept for one more version; behavior is unchanged.
95
+ */
81
96
  export function LoginFailureView({ title, description, primaryLabel, onPrimary, secondaryLabel, onSecondary, }) {
82
97
  const visibleTitle = legacyLoginLabel(title, LEGACY_LOGIN_FAILURE_DEFAULT_COPY.title);
83
98
  const visiblePrimaryLabel = legacyLoginLabel(primaryLabel, LEGACY_LOGIN_FAILURE_DEFAULT_COPY.primaryLabel);
@@ -0,0 +1,141 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type DeviceSummary } from './account-parts';
3
+ import { type PodSignInCopy, type PodSignInLocale } from './copy';
4
+ import { type NetworkPanelProps } from './NetworkPanel';
5
+ import type { AppIdentity, StorageLocation } from './types';
6
+ interface LocalizedProps {
7
+ locale?: PodSignInLocale;
8
+ copy?: Partial<PodSignInCopy>;
9
+ }
10
+ export interface WebIdEntry {
11
+ id: string;
12
+ displayName: string;
13
+ /** WebID URL; shown in monospace. */
14
+ webId: string;
15
+ avatarUrl?: string;
16
+ /** Omitted when the host cannot classify the storage location; never guessed. */
17
+ storage?: StorageLocation;
18
+ /** The Pod URL this WebID's data lives in; omitted when the host does not know it. */
19
+ podUrl?: string;
20
+ /** The device holding this WebID's Pod. */
21
+ deviceId?: string;
22
+ deviceName?: string;
23
+ authorizedAppCount?: number;
24
+ /** True when the host can delete this WebID's Pod; only then is the action offered. */
25
+ removable?: boolean;
26
+ }
27
+ /** A Pod the host knows about but cannot associate with any WebID. */
28
+ export interface UnlinkedPodEntry {
29
+ id: string;
30
+ /** Storage URL of the Pod; shown in monospace. Never a WebID. */
31
+ storageUrl: string;
32
+ displayName: string;
33
+ /** Omitted when the host cannot classify the storage location; never guessed. */
34
+ storage?: StorageLocation;
35
+ /** True when the host can delete this Pod; only then is the action offered. */
36
+ removable?: boolean;
37
+ }
38
+ export interface CreateWebIdFormProps extends LocalizedProps {
39
+ name: string;
40
+ onNameChange(name: string): void;
41
+ /** Preview of the WebID address for the typed name. */
42
+ addressPreview?: string;
43
+ nameHint?: {
44
+ tone: 'ok' | 'error';
45
+ text: string;
46
+ };
47
+ devices: DeviceSummary[];
48
+ selectedDeviceId: string;
49
+ onSelectDevice(deviceId: string): void;
50
+ /** Opens the shared "add device" dialog (owned by the host). */
51
+ onAddDevice(): void;
52
+ /** True while the Pod is being created. */
53
+ creating?: boolean;
54
+ /** True while Xpod on the selected device is being started. */
55
+ starting?: boolean;
56
+ /** Required for a device whose Xpod is stopped: it is started before anything is created. */
57
+ onStartDevice?(deviceId: string): void;
58
+ onSubmit(): void;
59
+ onCancel?(): void;
60
+ }
61
+ /**
62
+ * Two steps, like choosing a delivery address: a name, then a location shown
63
+ * as a summary card with "Change ›". Choosing happens in a picker dialog and
64
+ * involves no network settings.
65
+ */
66
+ export declare function CreateWebIdForm({ name, onNameChange, addressPreview, nameHint, devices, selectedDeviceId, onSelectDevice, onAddDevice, creating, starting, onStartDevice, onSubmit, onCancel, locale, copy: overrides, }: CreateWebIdFormProps): import("react").JSX.Element;
67
+ export interface WebIdSectionProps extends LocalizedProps {
68
+ webIds: WebIdEntry[];
69
+ /**
70
+ * Pods that exist but cannot be linked to any WebID. Shown as their own
71
+ * storage rows so real Pods never disappear just because the account has no
72
+ * matching identity binding. No WebID relationship is implied.
73
+ */
74
+ unlinkedPods?: UnlinkedPodEntry[];
75
+ /**
76
+ * Inline create form. Omitted by hosts that create WebIDs elsewhere (e.g. the
77
+ * read-only Account overview); `onCreateExternal` then provides the entry.
78
+ */
79
+ createForm?: CreateWebIdFormProps;
80
+ /** Open when arriving from authorization with no WebID; collapsed on a plain visit. */
81
+ createOpen?: boolean;
82
+ onCreateOpenChange?(open: boolean): void;
83
+ /** Shown instead of the inline form when the host creates WebIDs elsewhere. */
84
+ onCreateExternal?(): void;
85
+ /** Overrides the external-create button label; defaults to `createWebId`. */
86
+ createExternalLabel?: string;
87
+ onLinkExisting?(): void;
88
+ /** Jump to the device that holds a WebID's Pod. */
89
+ onGoToDevice?(deviceId: string): void;
90
+ /** Offered only for entries flagged `removable`. */
91
+ onRemoveStorage?(entry: WebIdEntry): void;
92
+ /** Overrides the remove action label; defaults to `revokeCredential` ("删除"). */
93
+ removeStorageLabel?: string;
94
+ /** Offered only for unlinked Pods flagged `removable`. */
95
+ onRemoveUnlinkedPod?(entry: UnlinkedPodEntry): void;
96
+ }
97
+ /** The identities of the account, one row per WebID with its Pod, plus the create form. */
98
+ export declare function WebIdSection({ webIds, createOpen, onCreateOpenChange, createForm, onCreateExternal, createExternalLabel, onLinkExisting, onGoToDevice, onRemoveStorage, removeStorageLabel, unlinkedPods, onRemoveUnlinkedPod, locale, copy: overrides, }: WebIdSectionProps): import("react").JSX.Element;
99
+ export interface DeviceSectionProps extends LocalizedProps {
100
+ devices: DeviceSummary[];
101
+ onAddDevice(): void;
102
+ /** The device whose network panel is expanded, if any. */
103
+ networkDeviceId?: string;
104
+ onToggleNetwork(deviceId: string): void;
105
+ /** Props of the panel shown under `networkDeviceId`. */
106
+ network?: Omit<NetworkPanelProps, 'locale' | 'copy'>;
107
+ /** Only offered for online devices whose Xpod is stopped, and only when the host can start it. */
108
+ onStartDevice?(deviceId: string): void;
109
+ startingDeviceId?: string;
110
+ }
111
+ /** Xpod Cloud and every edge device signed in to this account. Network belongs to the device. */
112
+ export declare function DeviceSection({ devices, onAddDevice, networkDeviceId, onToggleNetwork, network, onStartDevice, startingDeviceId, locale, copy: overrides, }: DeviceSectionProps): import("react").JSX.Element;
113
+ export interface CredentialEntry {
114
+ id: string;
115
+ label: string;
116
+ /** The WebID this credential acts as. */
117
+ webIdName: string;
118
+ createdLabel?: string;
119
+ }
120
+ export interface CredentialSectionProps extends LocalizedProps {
121
+ credentials: CredentialEntry[];
122
+ /** False when the account has no WebID yet. */
123
+ canCreate: boolean;
124
+ onCreate(): void;
125
+ onRevoke?(credentialId: string): void;
126
+ }
127
+ /** Solid client credentials: let a script or service reach a Pod as one WebID. */
128
+ export declare function CredentialSection({ credentials, canCreate, onCreate, onRevoke, locale, copy: overrides }: CredentialSectionProps): import("react").JSX.Element;
129
+ export interface ConsentResumeBannerProps extends LocalizedProps {
130
+ app: AppIdentity;
131
+ /** False until a Pod exists: the continue button stays disabled. */
132
+ podReady: boolean;
133
+ onContinue(): void;
134
+ onCancel(): void;
135
+ /** Replaces the default status sentence. */
136
+ statusText?: ReactNode;
137
+ pending?: boolean;
138
+ }
139
+ /** Top-of-page bar that leads back to authorization after managing the account. */
140
+ export declare function ConsentResumeBanner({ app, podReady, onContinue, onCancel, statusText, pending, locale, copy: overrides, }: ConsentResumeBannerProps): import("react").JSX.Element;
141
+ export {};
@@ -0,0 +1,67 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId, useState } from 'react';
3
+ import { ChevronRight, KeyRound, Server, UserRound } from 'lucide-react';
4
+ import { DeviceIdentity, listRowClass, sectionClass, SectionHeader, } from './account-parts.js';
5
+ import { formatCopy, resolvePodSignInCopy } from './copy.js';
6
+ import { DevicePickerDialog } from './DeviceDialogs.js';
7
+ import { NetworkPanel } from './NetworkPanel.js';
8
+ import { ActionButton, Field, Hostname, PodAvatar, Spinner, outlineButtonClass, primaryButtonClass, textButtonClass, } from './parts.js';
9
+ import { Input } from '../input.js';
10
+ import { cn } from '../utils.js';
11
+ const smallButtonClass = 'h-9 rounded-lg px-3 text-sm font-medium';
12
+ /**
13
+ * Two steps, like choosing a delivery address: a name, then a location shown
14
+ * as a summary card with "Change ›". Choosing happens in a picker dialog and
15
+ * involves no network settings.
16
+ */
17
+ export function CreateWebIdForm({ name, onNameChange, addressPreview, nameHint, devices, selectedDeviceId, onSelectDevice, onAddDevice, creating = false, starting = false, onStartDevice, onSubmit, onCancel, locale, copy: overrides, }) {
18
+ const copy = resolvePodSignInCopy(locale, overrides);
19
+ const [pickerOpen, setPickerOpen] = useState(false);
20
+ const selected = devices.find((device) => device.id === selectedDeviceId);
21
+ const needsStart = selected?.status === 'stopped';
22
+ const busy = creating || starting;
23
+ const trimmed = name.trim();
24
+ const submit = (event) => {
25
+ event.preventDefault();
26
+ if (busy || !selected)
27
+ return;
28
+ if (needsStart)
29
+ onStartDevice?.(selected.id);
30
+ else if (trimmed && nameHint?.tone !== 'error')
31
+ onSubmit();
32
+ };
33
+ return (_jsxs("form", { onSubmit: submit, "aria-label": copy.createWebId, "data-pod-sign-in": "create-webid", className: "flex flex-col gap-4 border-t border-border bg-muted p-4", noValidate: true, children: [_jsx(Field, { label: `1 · ${copy.createStepName}`, error: nameHint?.tone === 'error' ? nameHint.text : undefined, hint: nameHint?.tone === 'ok'
34
+ ? _jsx("span", { role: "status", children: nameHint.text })
35
+ : addressPreview ? _jsxs("span", { children: [_jsxs("span", { className: "sr-only", children: [copy.createNameHint, ": "] }), _jsx(Hostname, { children: addressPreview })] }) : undefined, children: (fieldProps) => (_jsx(Input, { ...fieldProps, name: "webIdName", type: "text", autoComplete: "off", autoCapitalize: "none", spellCheck: false, value: name, disabled: busy, onChange: (event) => onNameChange(event.target.value) })) }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx("p", { className: "text-[13px] font-medium text-foreground", children: `2 · ${copy.createStepLocation}` }), selected ? (_jsxs("div", { className: listRowClass, "data-pod-sign-in": "selected-device", children: [_jsx(DeviceIdentity, { device: selected, copy: copy }), _jsx("button", { type: "button", className: cn(textButtonClass, 'shrink-0 text-primary hover:underline'), disabled: busy, onClick: () => setPickerOpen(true), children: copy.changeDevice })] })) : null, _jsx("p", { className: "text-[13px] text-muted-foreground", children: copy.cannotChangeLater })] }), creating ? (_jsxs("p", { role: "status", "aria-live": "polite", className: "flex items-center gap-2 text-[13px] text-muted-foreground", children: [_jsx(Spinner, {}), copy.creatingNote] })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(ActionButton, { type: "submit", className: primaryButtonClass, busy: busy, disabled: !selected || (needsStart ? !onStartDevice : !trimmed || nameHint?.tone === 'error'), children: needsStart ? copy.startDeviceXpod : copy.createPod }), onCancel ? (_jsx(ActionButton, { variant: "outline", className: outlineButtonClass, disabled: busy, onClick: onCancel, children: copy.cancel })) : null] }), _jsx(DevicePickerDialog, { open: pickerOpen, onOpenChange: setPickerOpen, devices: devices, selectedDeviceId: selectedDeviceId, onSelect: onSelectDevice, onAddDevice: () => {
36
+ setPickerOpen(false);
37
+ onAddDevice();
38
+ }, locale: locale, copy: overrides })] }));
39
+ }
40
+ /** The identities of the account, one row per WebID with its Pod, plus the create form. */
41
+ export function WebIdSection({ webIds, createOpen = false, onCreateOpenChange, createForm, onCreateExternal, createExternalLabel, onLinkExisting, onGoToDevice, onRemoveStorage, removeStorageLabel, unlinkedPods, onRemoveUnlinkedPod, locale, copy: overrides, }) {
42
+ const copy = resolvePodSignInCopy(locale, overrides);
43
+ const titleId = useId();
44
+ return (_jsxs("section", { "aria-labelledby": titleId, "data-pod-sign-in": "webid-section", className: sectionClass, children: [_jsx(SectionHeader, { id: titleId, icon: UserRound, title: copy.webIdSectionTitle, hint: copy.webIdSectionHint, actions: (_jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [onLinkExisting ? (_jsx(ActionButton, { variant: "ghost", className: smallButtonClass, onClick: onLinkExisting, children: copy.linkExistingWebId })) : null, createForm && !createOpen ? (_jsx(ActionButton, { variant: "outline", className: smallButtonClass, onClick: () => onCreateOpenChange?.(true), children: copy.createWebId })) : null, !createForm && onCreateExternal ? (_jsx(ActionButton, { variant: "outline", className: smallButtonClass, onClick: onCreateExternal, children: createExternalLabel ?? copy.createWebId })) : null] })) }), webIds.length === 0 ? (_jsx("p", { className: "rounded-lg border border-border bg-card px-3 py-3 text-sm text-muted-foreground", children: copy.webIdEmpty })) : (_jsx("ul", { className: "flex flex-col gap-2", children: webIds.map((entry) => (_jsxs("li", { className: listRowClass, "data-webid-id": entry.id, children: [_jsx(PodAvatar, { name: entry.displayName, avatarUrl: entry.avatarUrl, storage: entry.storage, size: 40 }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("a", { className: "truncate text-sm font-medium text-primary hover:underline", href: entry.webId, target: "_blank", rel: "noopener noreferrer", children: entry.displayName }), _jsx(Hostname, { className: "truncate", children: entry.webId }), entry.podUrl ? (_jsx("a", { className: "truncate text-xs text-primary hover:underline", href: entry.podUrl, target: "_blank", rel: "noopener noreferrer", children: _jsx(Hostname, { children: entry.podUrl }) })) : null, _jsxs("span", { className: "flex flex-wrap items-center gap-x-3 text-xs text-muted-foreground", children: [entry.deviceName ? (entry.deviceId && onGoToDevice ? (_jsx("button", { type: "button", className: "rounded text-xs text-primary hover:underline", onClick: () => onGoToDevice(entry.deviceId), children: formatCopy(copy.podOnDevice, { device: entry.deviceName }) })) : _jsx("span", { children: formatCopy(copy.podOnDevice, { device: entry.deviceName }) })) : null, entry.authorizedAppCount !== undefined ? (_jsx("span", { children: formatCopy(copy.authorizedApps, { count: entry.authorizedAppCount }) })) : null] })] }), entry.removable && onRemoveStorage ? (_jsx("button", { type: "button", "aria-label": `${removeStorageLabel ?? copy.revokeCredential} ${entry.displayName}`, className: cn(textButtonClass, 'shrink-0 text-destructive hover:underline'), onClick: () => onRemoveStorage(entry), children: removeStorageLabel ?? copy.revokeCredential })) : null] }, entry.id))) })), unlinkedPods && unlinkedPods.length > 0 ? (_jsx("ul", { className: "flex flex-col gap-2", "data-pod-sign-in": "unlinked-pods", children: unlinkedPods.map((pod) => (_jsxs("li", { className: listRowClass, "data-unlinked-pod-id": pod.id, children: [_jsx(PodAvatar, { name: pod.displayName, storage: pod.storage, size: 40 }), _jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: pod.displayName }), _jsx("a", { className: "truncate text-xs text-primary hover:underline", href: pod.storageUrl, target: "_blank", rel: "noopener noreferrer", children: _jsx(Hostname, { children: pod.storageUrl }) })] }), pod.removable && onRemoveUnlinkedPod ? (_jsx("button", { type: "button", "aria-label": `${removeStorageLabel ?? copy.revokeCredential} ${pod.displayName}`, className: cn(textButtonClass, 'shrink-0 text-destructive hover:underline'), onClick: () => onRemoveUnlinkedPod(pod), children: removeStorageLabel ?? copy.revokeCredential })) : null] }, pod.id))) })) : null, createForm && createOpen ? (_jsx(CreateWebIdForm, { ...createForm, locale: createForm.locale ?? locale, copy: createForm.copy ?? overrides, onCancel: createForm.onCancel ?? (webIds.length > 0 ? () => onCreateOpenChange?.(false) : undefined) })) : null] }));
45
+ }
46
+ /** Xpod Cloud and every edge device signed in to this account. Network belongs to the device. */
47
+ export function DeviceSection({ devices, onAddDevice, networkDeviceId, onToggleNetwork, network, onStartDevice, startingDeviceId, locale, copy: overrides, }) {
48
+ const copy = resolvePodSignInCopy(locale, overrides);
49
+ const titleId = useId();
50
+ return (_jsxs("section", { "aria-labelledby": titleId, "data-pod-sign-in": "device-section", className: sectionClass, children: [_jsx(SectionHeader, { id: titleId, icon: Server, title: copy.deviceSectionTitle, hint: copy.deviceSectionHint, actions: _jsx(ActionButton, { variant: "outline", className: cn(smallButtonClass, 'shrink-0'), onClick: onAddDevice, children: copy.addDevice }) }), _jsx("ul", { className: "flex flex-col gap-2", children: devices.map((device) => {
51
+ const expanded = device.id === networkDeviceId;
52
+ const panelId = `${titleId}-network-${device.id}`;
53
+ return (_jsxs("li", { className: "flex flex-col gap-2", "data-device-row": device.id, children: [_jsxs("div", { className: listRowClass, children: [_jsx(DeviceIdentity, { device: device, copy: copy }), device.kind === 'edge' ? (_jsxs("button", { type: "button", "aria-expanded": expanded, "aria-controls": expanded ? panelId : undefined, className: cn(textButtonClass, 'shrink-0 text-primary hover:underline'), onClick: () => onToggleNetwork(device.id), children: [expanded ? copy.collapseAction : device.status === 'unreachable' ? copy.fixAction : copy.networkAction, _jsx(ChevronRight, { "aria-hidden": "true", className: cn('ml-0.5 inline h-3.5 w-3.5 transition-transform', expanded && 'rotate-90') })] })) : null, device.status === 'stopped' && onStartDevice ? (_jsx(ActionButton, { variant: "outline", className: cn(smallButtonClass, 'shrink-0'), busy: startingDeviceId === device.id, onClick: () => onStartDevice(device.id), children: copy.startAction })) : null] }), expanded && network ? (_jsx("div", { id: panelId, children: _jsx(NetworkPanel, { ...network, locale: locale, copy: overrides }) })) : null] }, device.id));
54
+ }) })] }));
55
+ }
56
+ /** Solid client credentials: let a script or service reach a Pod as one WebID. */
57
+ export function CredentialSection({ credentials, canCreate, onCreate, onRevoke, locale, copy: overrides }) {
58
+ const copy = resolvePodSignInCopy(locale, overrides);
59
+ const titleId = useId();
60
+ return (_jsxs("section", { "aria-labelledby": titleId, "data-pod-sign-in": "credential-section", className: sectionClass, children: [_jsx(SectionHeader, { id: titleId, icon: KeyRound, title: copy.credentialSectionTitle, hint: copy.credentialSectionHint, actions: _jsx(ActionButton, { variant: "outline", className: cn(smallButtonClass, 'shrink-0'), disabled: !canCreate, onClick: onCreate, children: copy.createCredential }) }), credentials.length === 0 ? (_jsx("p", { className: "rounded-lg border border-border bg-card px-3 py-3 text-sm text-muted-foreground", children: copy.credentialEmpty })) : (_jsx("ul", { className: "flex flex-col gap-2", children: credentials.map((credential) => (_jsxs("li", { className: listRowClass, children: [_jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: credential.label }), _jsxs("span", { className: "truncate text-xs text-muted-foreground", children: [credential.webIdName, credential.createdLabel ? ` · ${credential.createdLabel}` : ''] })] }), onRevoke ? (_jsx("button", { type: "button", "aria-label": `${copy.revokeCredential} ${credential.label}`, className: cn(textButtonClass, 'shrink-0 text-destructive hover:underline'), onClick: () => onRevoke(credential.id), children: copy.revokeCredential })) : null] }, credential.id))) }))] }));
61
+ }
62
+ /** Top-of-page bar that leads back to authorization after managing the account. */
63
+ export function ConsentResumeBanner({ app, podReady, onContinue, onCancel, statusText, pending = false, locale, copy: overrides, }) {
64
+ const copy = resolvePodSignInCopy(locale, overrides);
65
+ const titleId = useId();
66
+ return (_jsxs("section", { "aria-labelledby": titleId, "data-pod-sign-in": "consent-resume", className: "pod-sign-in flex flex-wrap items-center gap-3 rounded-lg bg-muted px-4 py-3 text-foreground", children: [app.icon ? (_jsx("span", { "aria-hidden": "true", className: "flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden rounded-lg", children: app.icon })) : null, _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("p", { id: titleId, className: "text-sm font-semibold", children: formatCopy(copy.resumeTitle, { app: app.name }) }), _jsx("p", { role: "status", "aria-live": "polite", className: "text-[13px] text-muted-foreground", children: statusText ?? (podReady ? copy.resumeReady : copy.resumeWaiting) })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx("button", { type: "button", className: cn(textButtonClass, 'text-primary hover:underline'), disabled: pending, onClick: onCancel, children: copy.resumeCancel }), _jsx(ActionButton, { className: "h-11 rounded-lg px-4 text-sm font-medium", busy: pending, disabled: !podReady, onClick: onContinue, children: formatCopy(copy.resumeContinue, { app: app.name }) })] })] }));
67
+ }
@@ -0,0 +1,53 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type IdpViewCommonProps } from './IdpViews';
3
+ import type { StorageLocation } from './types';
4
+ export interface ConsentWebId {
5
+ id: string;
6
+ displayName: string;
7
+ shortName: string;
8
+ /** Full WebID; shown only in the folded request details. */
9
+ webId?: string;
10
+ avatarUrl?: string;
11
+ /** Where the data of this WebID lives; expressed only as the avatar badge. */
12
+ storage: StorageLocation;
13
+ }
14
+ export interface ConsentViewProps extends IdpViewCommonProps {
15
+ app: {
16
+ name: string;
17
+ icon?: ReactNode;
18
+ host: string;
19
+ clientId?: string;
20
+ verified: boolean;
21
+ };
22
+ webIds: ConsentWebId[];
23
+ selectedWebId: string;
24
+ scopes: Array<{
25
+ id: string;
26
+ label: string;
27
+ }>;
28
+ rememberChoice: boolean;
29
+ pending?: 'approve' | 'deny';
30
+ /** Disables both actions (something else is in flight); unlike `pending` it spins nothing. */
31
+ disabled?: boolean;
32
+ /** Disables only "Allow", e.g. while no WebID is chosen yet. */
33
+ approveDisabled?: boolean;
34
+ /**
35
+ * Hosts whose automation drives the WebID choice through a native select (the
36
+ * CSS consent form's `oidc-consent-webid`) get a visually hidden select with this
37
+ * id, mirroring the radio group. It is skipped for assistive technology.
38
+ */
39
+ automationSelectId?: string;
40
+ /** "Manage account": leave for the account page and come back. */
41
+ onManageAccount?(): void;
42
+ /** "Switch account": sign out of the account. */
43
+ onSwitchAccount?(): void;
44
+ onSelectWebId(id: string): void;
45
+ onRememberChange(value: boolean): void;
46
+ onApprove(): void;
47
+ onDeny(): void;
48
+ }
49
+ /**
50
+ * B4: consent. Three roles in fixed places: the sign-in service (top bar), the
51
+ * requesting application (centered), and the identity being granted (radio list).
52
+ */
53
+ export declare function ConsentView(props: ConsentViewProps): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { TriangleAlert } from 'lucide-react';
3
+ import { useId } from 'react';
4
+ import { formatCopy, resolvePodSignInCopy } from './copy.js';
5
+ import { IdpFrameHeader } from './IdpViews.js';
6
+ import { ActionButton, ScreenLayout, CheckboxRow, Disclosure, Hostname, PodAvatar, outlineButtonClass, primaryButtonClass, textButtonClass } from './parts.js';
7
+ import { cn } from '../utils.js';
8
+ /**
9
+ * B4: consent. Three roles in fixed places: the sign-in service (top bar), the
10
+ * requesting application (centered), and the identity being granted (radio list).
11
+ */
12
+ export function ConsentView(props) {
13
+ const { app, webIds, selectedWebId, scopes, rememberChoice, pending, disabled = false, approveDisabled = false, automationSelectId, onSelectWebId, onRememberChange, onApprove, onDeny, onManageAccount, onSwitchAccount, } = props;
14
+ const copy = resolvePodSignInCopy(props.locale, props.copy);
15
+ const labelId = useId();
16
+ const busy = pending !== undefined || disabled;
17
+ const single = webIds.length === 1;
18
+ const selectedFullWebId = (webIds.find((webId) => webId.id === selectedWebId) ?? webIds[0])?.webId;
19
+ const actions = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsx(ActionButton, { variant: "outline", className: outlineButtonClass, busy: pending === 'deny', disabled: busy, onClick: onDeny, children: copy.deny }), _jsx(ActionButton, { className: primaryButtonClass, busy: pending === 'approve', disabled: busy || approveDisabled, onClick: onApprove, children: copy.allow })] }), onManageAccount || onSwitchAccount ? (_jsxs("div", { className: "flex flex-wrap justify-center gap-2", children: [onManageAccount ? (_jsx("button", { type: "button", className: cn(textButtonClass, 'text-muted-foreground hover:text-foreground'), disabled: busy, onClick: onManageAccount, children: copy.manageAccount })) : null, onSwitchAccount ? (_jsx("button", { type: "button", className: cn(textButtonClass, 'text-muted-foreground hover:text-foreground'), disabled: busy, onClick: onSwitchAccount, children: copy.switchAccount })) : null] })) : null] }));
20
+ return (_jsxs(ScreenLayout, { "data-pod-sign-in-state": "consent", chrome: _jsx(IdpFrameHeader, { ...props }), actions: actions, children: [_jsxs("div", { className: "flex flex-col items-center gap-2 text-center", children: [app.icon ? (_jsx("span", { "aria-hidden": "true", className: "flex h-12 w-12 items-center justify-center overflow-hidden rounded-xl", children: app.icon })) : null, _jsx("h1", { className: "text-[17px] font-semibold text-foreground", children: formatCopy(copy.consentTitle, { app: app.name }) }), app.host ? _jsx(Hostname, { children: app.host }) : null, !app.verified ? (_jsxs("p", { role: "alert", className: "flex items-start gap-1.5 text-left text-[13px] text-warning", children: [_jsx(TriangleAlert, { "aria-hidden": "true", className: "mt-0.5 h-3.5 w-3.5 shrink-0" }), copy.unverifiedWarning] })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { id: labelId, className: "text-[13px] font-medium text-foreground", children: copy.chooseWebId }), single ? (_jsx("div", { className: "rounded-lg border border-border bg-card p-3", children: _jsx(WebIdRow, { webId: webIds[0] }) })) : (_jsx("div", { role: "radiogroup", "aria-labelledby": labelId, className: "flex flex-col gap-2", children: webIds.map((webId) => {
21
+ const selected = webId.id === selectedWebId;
22
+ return (_jsxs("label", { className: cn('flex min-h-11 cursor-pointer items-center gap-3 rounded-lg border p-3 focus-within:outline focus-within:outline-2 focus-within:outline-offset-2 focus-within:outline-ring', selected ? 'border-primary bg-accent' : 'border-border bg-card'), children: [_jsx("input", { type: "radio", name: "consent-webid", className: "sr-only", value: webId.id, checked: selected, disabled: busy, onChange: () => onSelectWebId(webId.id) }), _jsx(WebIdRow, { webId: webId })] }, webId.id));
23
+ }) })), !single && automationSelectId ? (_jsxs("select", { id: automationSelectId, "aria-hidden": "true", "aria-label": copy.chooseWebId, tabIndex: -1, className: "sr-only", value: selectedWebId, disabled: busy, onChange: (event) => onSelectWebId(event.target.value), children: [!selectedWebId ? _jsx("option", { value: "", disabled: true, children: copy.chooseWebId }) : null, webIds.map((webId) => _jsxs("option", { value: webId.id, children: [webId.displayName, " ", webId.shortName] }, webId.id))] })) : null, _jsx("p", { className: "text-[13px] leading-5 text-muted-foreground", children: formatCopy(copy.consentConsequence, { app: app.name }) })] }), _jsx(Disclosure, { summary: copy.requestDetails, children: _jsxs("div", { className: "flex flex-col gap-2", children: [scopes.length > 0 ? (_jsxs("div", { children: [_jsx("p", { className: "font-medium text-foreground", children: copy.scopes }), _jsx("ul", { className: "list-disc pl-5", children: scopes.map((scope) => _jsx("li", { children: scope.label }, scope.id)) })] })) : null, selectedFullWebId ? (_jsxs("p", { children: [_jsx("span", { className: "font-medium text-foreground", children: copy.webIdFull }), ' ', _jsx(Hostname, { className: "break-all", children: selectedFullWebId })] })) : null, app.clientId ? (_jsxs("p", { children: [_jsx("span", { className: "font-medium text-foreground", children: copy.clientId }), ' ', _jsx(Hostname, { className: "break-all", children: app.clientId })] })) : null, app.verified ? (_jsx(CheckboxRow, { checked: rememberChoice, onChange: onRememberChange, label: copy.rememberChoice, disabled: busy })) : null] }) })] }));
24
+ }
25
+ function WebIdRow({ webId }) {
26
+ return (_jsxs("span", { className: "flex min-w-0 items-center gap-3", "data-pod-sign-in": "webid-row", children: [_jsx(PodAvatar, { name: webId.displayName, avatarUrl: webId.avatarUrl, storage: webId.storage, size: 40 }), _jsxs("span", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate text-sm font-medium text-foreground", children: webId.displayName }), webId.shortName !== webId.displayName ? (_jsx("span", { className: "truncate font-mono text-xs text-muted-foreground", children: webId.shortName })) : null] })] }));
27
+ }
@@ -0,0 +1,40 @@
1
+ import { type DeviceSummary } from './account-parts';
2
+ import { type PodSignInCopy, type PodSignInLocale } from './copy';
3
+ import { type NetworkPanelProps } from './NetworkPanel';
4
+ interface LocalizedProps {
5
+ locale?: PodSignInLocale;
6
+ copy?: Partial<PodSignInCopy>;
7
+ }
8
+ export interface DevicePickerDialogProps extends LocalizedProps {
9
+ open: boolean;
10
+ onOpenChange(open: boolean): void;
11
+ devices: DeviceSummary[];
12
+ selectedDeviceId?: string;
13
+ /** Picking a row selects it and closes the dialog. */
14
+ onSelect(deviceId: string): void;
15
+ onAddDevice(): void;
16
+ }
17
+ /** "Choose where to store": pick a row, like choosing a delivery address. Offline devices are greyed out. */
18
+ export declare function DevicePickerDialog({ open, onOpenChange, devices, selectedDeviceId, onSelect, onAddDevice, locale, copy: overrides, }: DevicePickerDialogProps): import("react").JSX.Element;
19
+ export type AddDeviceStep = 1 | 2 | 3;
20
+ export interface AddDeviceDialogProps extends LocalizedProps {
21
+ open: boolean;
22
+ onOpenChange(open: boolean): void;
23
+ step: AddDeviceStep;
24
+ /** Only when the host can launch Xpod Edge on this computer, and it has not joined yet. */
25
+ canJoinThisComputer?: boolean;
26
+ joining?: boolean;
27
+ onJoinThisComputer?(): void;
28
+ /** The device that came online (steps 2 and 3). */
29
+ device?: DeviceSummary;
30
+ /** Network checks and tunnel form for step 2. `onSkip` is supplied by the dialog. */
31
+ network: Omit<NetworkPanelProps, 'onSkip' | 'locale' | 'copy'>;
32
+ /** Step 2: leave the network as is and finish ("Skip, set up later", or "Continue" once reachable). */
33
+ onNetworkDone(): void;
34
+ /** Where the dialog was opened from decides the final button: "Done" or "Use this device". */
35
+ origin: 'device-section' | 'picker';
36
+ onFinish(): void;
37
+ }
38
+ /** Add a device in three steps: install and sign in, check the network, done. */
39
+ export declare function AddDeviceDialog({ open, onOpenChange, step, canJoinThisComputer, joining, onJoinThisComputer, device, network, onNetworkDone, origin, onFinish, locale, copy: overrides, }: AddDeviceDialogProps): import("react").JSX.Element;
40
+ export {};
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Dialog, DialogContent, DialogTitle } from '../dialog.js';
3
+ import { cn } from '../utils.js';
4
+ import { DeviceIdentity } from './account-parts.js';
5
+ import { resolvePodSignInCopy } from './copy.js';
6
+ import { NetworkPanel } from './NetworkPanel.js';
7
+ import { ActionButton, outlineButtonClass, primaryButtonClass } from './parts.js';
8
+ const dialogClass = 'pod-sign-in max-h-[90dvh] w-[min(400px,calc(100vw-2rem))] max-w-[400px] gap-4 overflow-y-auto rounded-xl bg-card p-5';
9
+ /** "Choose where to store": pick a row, like choosing a delivery address. Offline devices are greyed out. */
10
+ export function DevicePickerDialog({ open, onOpenChange, devices, selectedDeviceId, onSelect, onAddDevice, locale, copy: overrides, }) {
11
+ const copy = resolvePodSignInCopy(locale, overrides);
12
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: dialogClass, "aria-describedby": undefined, children: [_jsx(DialogTitle, { className: "text-[17px] leading-6", children: copy.devicePickerTitle }), _jsx("div", { role: "radiogroup", "aria-label": copy.devicePickerTitle, className: "flex flex-col gap-2", children: devices.map((device) => {
13
+ const selectable = device.status !== 'offline';
14
+ const selected = device.id === selectedDeviceId;
15
+ return (_jsx("button", { type: "button", role: "radio", "aria-checked": selected, disabled: !selectable, className: cn('flex min-h-14 w-full items-center rounded-lg border px-3 py-2 text-left', selected ? 'border-primary bg-accent' : 'border-border bg-card', selectable ? 'hover:bg-accent' : 'cursor-not-allowed opacity-50'), onClick: () => {
16
+ onSelect(device.id);
17
+ onOpenChange(false);
18
+ }, children: _jsx(DeviceIdentity, { device: device, copy: copy }) }, device.id));
19
+ }) }), _jsx(ActionButton, { variant: "outline", className: outlineButtonClass, onClick: onAddDevice, children: copy.addDevice })] }) }));
20
+ }
21
+ /** Add a device in three steps: install and sign in, check the network, done. */
22
+ export function AddDeviceDialog({ open, onOpenChange, step, canJoinThisComputer = false, joining = false, onJoinThisComputer, device, network, onNetworkDone, origin, onFinish, locale, copy: overrides, }) {
23
+ const copy = resolvePodSignInCopy(locale, overrides);
24
+ const steps = [copy.stepInstall, copy.stepNetwork, copy.stepDone];
25
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: dialogClass, "aria-describedby": undefined, children: [_jsx(DialogTitle, { className: "text-[17px] leading-6", children: copy.addDeviceTitle }), _jsx("ol", { className: "flex items-center gap-2 text-[13px]", "aria-label": copy.addDeviceTitle, children: steps.map((label, index) => {
26
+ const number = index + 1;
27
+ const state = number === step ? 'current' : number < step ? 'done' : 'todo';
28
+ return (_jsxs("li", { "aria-current": state === 'current' ? 'step' : undefined, "data-step-state": state, className: cn('flex min-w-0 items-center gap-1', state === 'current' ? 'font-semibold text-foreground' : 'text-muted-foreground'), children: [_jsx("span", { "aria-hidden": "true", className: cn('flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-xs', state === 'todo' ? 'bg-muted' : 'bg-primary text-primary-foreground'), children: number }), _jsx("span", { className: "truncate", children: label })] }, label));
29
+ }) }), step === 1 ? (_jsxs("div", { className: "flex flex-col gap-4", children: [canJoinThisComputer ? (_jsx(ActionButton, { className: primaryButtonClass, busy: joining, onClick: onJoinThisComputer, children: copy.joinThisComputer })) : null, _jsx("p", { className: "text-sm text-foreground", children: copy.installOnOther }), _jsx("p", { role: "status", "aria-live": "polite", className: "text-[13px] text-muted-foreground", children: copy.waitingForDevice })] })) : null, step === 2 ? (_jsxs("div", { className: "flex flex-col gap-4", children: [device ? _jsx(DeviceOnlineCard, { device: device, copy: copy }) : null, network.checks.wan === 'failed' ? (_jsx(NetworkPanel, { ...network, locale: locale, copy: overrides, onSkip: onNetworkDone })) : (_jsxs(_Fragment, { children: [_jsx(NetworkPanel, { ...network, locale: locale, copy: overrides }), _jsx(ActionButton, { className: primaryButtonClass, disabled: network.checks.wan === 'checking', onClick: onNetworkDone, children: copy.customSubmit })] }))] })) : null, step === 3 ? (_jsxs("div", { className: "flex flex-col gap-4", children: [device ? _jsx(DeviceOnlineCard, { device: device, copy: copy }) : null, _jsx(ActionButton, { className: primaryButtonClass, onClick: onFinish, children: origin === 'picker' ? copy.useThisDevice : copy.finish })] })) : null] }) }));
30
+ }
31
+ function DeviceOnlineCard({ device, copy }) {
32
+ return (_jsxs("div", { className: "flex items-center gap-3 rounded-lg border border-border bg-card p-3", "data-pod-sign-in": "new-device", children: [_jsx(DeviceIdentity, { device: device, copy: copy, showStatus: false }), _jsx("span", { role: "status", className: "shrink-0 text-[13px] text-success", children: copy.deviceOnline })] }));
33
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type PodSignInLocale } from './copy';
3
+ export interface IdpChromeProps {
4
+ /** The sign-in service, e.g. "Xpod". */
5
+ serviceName: string;
6
+ /** Shown in monospace; this row is what tells a user which service's page this is. */
7
+ serviceHost?: string;
8
+ /** Defaults to the Xpod mark. */
9
+ icon?: ReactNode;
10
+ /** Word after the name (default "账号服务" / "Sign-in service"). */
11
+ serviceLabel?: string;
12
+ locale?: PodSignInLocale;
13
+ }
14
+ /** 44px tinted bar at the top of every sign-in-service page (B group). */
15
+ export declare function IdpChrome({ serviceName, serviceHost, icon, serviceLabel, locale }: IdpChromeProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { resolvePodSignInCopy } from './copy.js';
3
+ import { XpodMark } from './XpodMark.js';
4
+ /** 44px tinted bar at the top of every sign-in-service page (B group). */
5
+ export function IdpChrome({ serviceName, serviceHost, icon, serviceLabel, locale }) {
6
+ const label = serviceLabel ?? resolvePodSignInCopy(locale).serviceLabel;
7
+ return (_jsxs("div", { "data-pod-sign-in": "idp-chrome", className: "flex h-11 shrink-0 items-center gap-2 border-b border-border bg-[hsl(var(--sunken))] px-4 text-[13px] text-foreground", children: [_jsx("span", { "aria-hidden": "true", className: "flex h-6 w-6 shrink-0 items-center justify-center overflow-hidden rounded-md", children: icon ?? _jsx(XpodMark, { size: 24 }) }), _jsxs("span", { className: "min-w-0 truncate font-medium", children: [serviceName, " \u00B7 ", label] }), serviceHost ? _jsx("span", { className: "ml-auto truncate font-mono text-xs text-muted-foreground", children: serviceHost }) : null] }));
8
+ }
@@ -0,0 +1,93 @@
1
+ import { type ReactNode } from 'react';
2
+ import { type PodSignInCopy, type PodSignInLocale } from './copy';
3
+ /** Shared by every B-group view: which service's page this is, and the wording. */
4
+ export interface IdpViewCommonProps {
5
+ /** Shown in the tinted top bar together with `serviceHost`. */
6
+ serviceName?: string;
7
+ serviceHost?: string;
8
+ serviceIcon?: ReactNode;
9
+ locale?: PodSignInLocale;
10
+ copy?: Partial<PodSignInCopy>;
11
+ }
12
+ export declare function IdpFrameHeader({ serviceName, serviceHost, serviceIcon, locale, copy }: IdpViewCommonProps): import("react").JSX.Element;
13
+ export interface IdpSignInViewProps extends IdpViewCommonProps {
14
+ serviceName: string;
15
+ returnToAppName?: string;
16
+ /** Form-level failure, announced assertively. */
17
+ error?: string;
18
+ fieldErrors?: {
19
+ email?: string;
20
+ password?: string;
21
+ };
22
+ pending?: boolean;
23
+ remember: boolean;
24
+ /** Uncontrolled initial values (a previously used email, for instance). */
25
+ defaultEmail?: string;
26
+ defaultPassword?: string;
27
+ /** Fires on every edit so a host can clear its own error state. */
28
+ onFieldChange?(field: 'email' | 'password', value: string): void;
29
+ /** Shown only when the host offers a remember choice; omit to hide the checkbox. */
30
+ onRememberChange?(value: boolean): void;
31
+ onSubmit(values: {
32
+ email: string;
33
+ password: string;
34
+ }): void;
35
+ /** Hidden when omitted. `forgotHref` renders a real link instead, for hosts that navigate to a page. */
36
+ onForgot?(): void;
37
+ forgotHref?: string;
38
+ /** Hidden when omitted. `registerHref` renders a real link instead. */
39
+ onRegister?(): void;
40
+ registerHref?: string;
41
+ onUseOtherSolid?(): void;
42
+ }
43
+ /** B1: sign in to the sign-in service. Email and password only ever appear here. */
44
+ export declare function IdpSignInView(props: IdpSignInViewProps): import("react").JSX.Element;
45
+ export interface IdpRegisterViewProps extends IdpViewCommonProps {
46
+ serviceName: string;
47
+ returnToAppName?: string;
48
+ /** e.g. "将创建你的 WebID 和 Pod:pod.undefineds.co/xiaolin/" */
49
+ usernamePreview?: string;
50
+ /** Decided by the service's CSS controls. */
51
+ requireUsername: boolean;
52
+ fieldErrors?: Partial<Record<'username' | 'email' | 'password', string | undefined>>;
53
+ /** Form-level failure, announced assertively. */
54
+ error?: string;
55
+ pending?: boolean;
56
+ defaultEmail?: string;
57
+ defaultPassword?: string;
58
+ defaultUsername?: string;
59
+ /** Fires on every edit so a host can check the name and clear its own errors. */
60
+ onFieldChange?(field: 'username' | 'email' | 'password', value: string): void;
61
+ onSubmit(values: {
62
+ username?: string;
63
+ email: string;
64
+ password: string;
65
+ }): void;
66
+ /** Hidden when omitted. */
67
+ onSignIn?(): void;
68
+ }
69
+ /** B2: register. The explicit form is what makes creating a WebID and Pod not implicit. */
70
+ export declare function IdpRegisterView(props: IdpRegisterViewProps): import("react").JSX.Element;
71
+ export interface IdpNoWebIdViewProps extends IdpViewCommonProps {
72
+ appName: string;
73
+ /**
74
+ * The name prefilled in the field. Omit it when the host cannot create a WebID
75
+ * here: the name field is then not shown, and the primary action just calls `onCreate('')`.
76
+ */
77
+ defaultName?: string;
78
+ /** Live availability of the name; ok is announced politely, error assertively. */
79
+ nameHint?: {
80
+ tone: 'ok' | 'muted' | 'error';
81
+ text: string;
82
+ };
83
+ /** Form-level failure (e.g. a creation request failed), assertive and not blocking a retry. */
84
+ error?: string;
85
+ pending?: boolean;
86
+ /** Fired on every edit so the host can check availability live. */
87
+ onNameChange?(name: string): void;
88
+ onCreate(name: string): void;
89
+ /** Hidden when omitted. */
90
+ onChooseOtherLocation?(): void;
91
+ }
92
+ /** B3: the account has no WebID. Create one here, or leave for the account page to store it elsewhere. */
93
+ export declare function IdpNoWebIdView(props: IdpNoWebIdViewProps): import("react").JSX.Element;