@peers-app/peers-ui 0.25.1 → 0.25.3

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 (177) hide show
  1. package/README.md +2 -1
  2. package/dist/components/markdown-editor/mention-node.d.ts +4 -0
  3. package/dist/components/markdown-editor/mention-node.js +4 -1
  4. package/dist/components/responsive-section-nav.d.ts +34 -0
  5. package/dist/components/responsive-section-nav.js +19 -0
  6. package/dist/components/responsive-section-nav.test.d.ts +1 -0
  7. package/dist/components/responsive-section-nav.test.js +45 -0
  8. package/dist/components/router.d.ts +2 -3
  9. package/dist/components/router.js +6 -9
  10. package/dist/globals-hash.test.d.ts +1 -0
  11. package/dist/globals-hash.test.js +16 -0
  12. package/dist/globals.d.ts +19 -0
  13. package/dist/globals.js +51 -12
  14. package/dist/hooks/use-host-contract.d.ts +18 -0
  15. package/dist/hooks/use-host-contract.js +53 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/peers-services-registration.d.ts +1 -5
  19. package/dist/peers-services-registration.js +6 -16
  20. package/dist/root-layout/root-layout-host.js +7 -4
  21. package/dist/root-layout/root-layout-host.test.js +3 -6
  22. package/dist/screens/account/account-screen.d.ts +1 -0
  23. package/dist/screens/account/account-screen.js +29 -53
  24. package/dist/screens/account/account-screen.test.d.ts +1 -0
  25. package/dist/screens/account/account-screen.test.js +47 -0
  26. package/dist/screens/contacts/contact-details.js +2 -23
  27. package/dist/screens/contacts/contact-list.d.ts +24 -1
  28. package/dist/screens/contacts/contact-list.js +128 -143
  29. package/dist/screens/contacts/contact-list.test.d.ts +1 -0
  30. package/dist/screens/contacts/contact-list.test.js +48 -0
  31. package/dist/screens/contacts/index.d.ts +0 -2
  32. package/dist/screens/contacts/index.js +0 -3
  33. package/dist/screens/device-pairing/device-pairing.js +17 -16
  34. package/dist/screens/device-pairing/device-pairing.test.js +4 -9
  35. package/dist/screens/device-pairing/identity-ipc.d.ts +11 -0
  36. package/dist/screens/device-pairing/identity-ipc.js +20 -0
  37. package/dist/screens/device-pairing/use-device-pairing-state.d.ts +2 -2
  38. package/dist/screens/device-pairing/use-device-pairing-state.js +7 -25
  39. package/dist/screens/devices/devices-list.d.ts +18 -0
  40. package/dist/screens/devices/devices-list.js +173 -0
  41. package/dist/screens/devices/devices-list.test.d.ts +1 -0
  42. package/dist/screens/devices/devices-list.test.js +53 -0
  43. package/dist/screens/devices/index.d.ts +1 -0
  44. package/dist/screens/devices/index.js +5 -0
  45. package/dist/screens/groups/group-details.d.ts +1 -0
  46. package/dist/screens/groups/group-details.js +4 -63
  47. package/dist/screens/groups/group-invite-panel.js +2 -2
  48. package/dist/screens/groups/group-list.d.ts +18 -1
  49. package/dist/screens/groups/group-list.js +107 -136
  50. package/dist/screens/groups/group-list.test.d.ts +1 -0
  51. package/dist/screens/groups/group-list.test.js +38 -0
  52. package/dist/screens/groups/group-members.d.ts +1 -0
  53. package/dist/screens/groups/group-members.js +2 -1
  54. package/dist/screens/groups/index.d.ts +0 -3
  55. package/dist/screens/groups/index.js +0 -4
  56. package/dist/screens/identity/identity-routes.d.ts +107 -0
  57. package/dist/screens/identity/identity-routes.js +245 -0
  58. package/dist/screens/identity/identity-routes.test.d.ts +1 -0
  59. package/dist/screens/identity/identity-routes.test.js +51 -0
  60. package/dist/screens/identity/identity-screen.d.ts +9 -0
  61. package/dist/screens/identity/identity-screen.js +143 -0
  62. package/dist/screens/identity/identity-screen.test.d.ts +1 -0
  63. package/dist/screens/identity/identity-screen.test.js +103 -0
  64. package/dist/screens/identity/index.d.ts +2 -0
  65. package/dist/screens/identity/index.js +6 -0
  66. package/dist/screens/identity/invite-contact-panel.d.ts +4 -0
  67. package/dist/screens/identity/invite-contact-panel.js +30 -0
  68. package/dist/screens/identity/profile-invite.d.ts +7 -0
  69. package/dist/screens/identity/profile-invite.js +62 -0
  70. package/dist/screens/invites/accept-invite.js +1 -1
  71. package/dist/screens/invites/activity-view.d.ts +24 -0
  72. package/dist/screens/invites/activity-view.js +64 -0
  73. package/dist/screens/invites/activity-view.test.d.ts +1 -0
  74. package/dist/screens/invites/activity-view.test.js +108 -0
  75. package/dist/screens/invites/index.d.ts +1 -2
  76. package/dist/screens/invites/index.js +1 -3
  77. package/dist/screens/invites/invite-capture.d.ts +0 -2
  78. package/dist/screens/invites/invite-capture.js +1 -3
  79. package/dist/screens/invites/invite-classify.test.d.ts +1 -0
  80. package/dist/screens/invites/invite-classify.test.js +98 -0
  81. package/dist/screens/invites/invites-ipc.d.ts +1 -1
  82. package/dist/screens/invites/invites-ipc.js +5 -45
  83. package/dist/screens/invites/pending-invites-list.d.ts +35 -16
  84. package/dist/screens/invites/pending-invites-list.js +69 -30
  85. package/dist/screens/network-viewer/network-viewer-ipc.js +4 -59
  86. package/dist/screens/search/global-search.d.ts +1 -0
  87. package/dist/screens/search/global-search.js +2 -1
  88. package/dist/screens/settings/settings-page.js +25 -58
  89. package/dist/screens/setup-user.js +5 -5
  90. package/dist/screens/tools/tool-access-ipc.d.ts +2 -0
  91. package/dist/screens/tools/tool-access-ipc.js +8 -55
  92. package/dist/screens/welcome-modal.js +3 -2
  93. package/dist/system-apps/identity.app.d.ts +3 -0
  94. package/dist/system-apps/identity.app.js +10 -0
  95. package/dist/system-apps/index.d.ts +1 -4
  96. package/dist/system-apps/index.js +5 -17
  97. package/dist/tabs-layout/network-status-indicator.js +10 -3
  98. package/dist/tabs-layout/network-status-indicator.test.js +15 -0
  99. package/dist/tabs-layout/tabs-layout.js +3 -1
  100. package/dist/tabs-layout/tabs-state.d.ts +79 -1
  101. package/dist/tabs-layout/tabs-state.js +256 -79
  102. package/dist/tabs-layout/tabs-state.test.d.ts +1 -0
  103. package/dist/tabs-layout/tabs-state.test.js +110 -0
  104. package/package.json +3 -3
  105. package/src/components/markdown-editor/mention-node.ts +5 -1
  106. package/src/components/responsive-section-nav.test.tsx +88 -0
  107. package/src/components/responsive-section-nav.tsx +92 -0
  108. package/src/components/router.tsx +6 -9
  109. package/src/globals-hash.test.ts +19 -0
  110. package/src/globals.tsx +53 -13
  111. package/src/hooks/use-host-contract.ts +75 -0
  112. package/src/index.tsx +1 -0
  113. package/src/peers-services-registration.ts +6 -17
  114. package/src/root-layout/root-layout-host.test.tsx +4 -7
  115. package/src/root-layout/root-layout-host.tsx +9 -11
  116. package/src/screens/account/account-screen.test.tsx +61 -0
  117. package/src/screens/account/account-screen.tsx +116 -95
  118. package/src/screens/contacts/contact-details.tsx +4 -26
  119. package/src/screens/contacts/contact-list.test.tsx +59 -0
  120. package/src/screens/contacts/contact-list.tsx +246 -260
  121. package/src/screens/contacts/index.ts +0 -4
  122. package/src/screens/device-pairing/device-pairing.test.tsx +7 -9
  123. package/src/screens/device-pairing/device-pairing.tsx +19 -23
  124. package/src/screens/device-pairing/identity-ipc.ts +25 -0
  125. package/src/screens/device-pairing/use-device-pairing-state.ts +10 -33
  126. package/src/screens/devices/devices-list.test.tsx +68 -0
  127. package/src/screens/devices/devices-list.tsx +361 -0
  128. package/src/screens/devices/index.ts +1 -0
  129. package/src/screens/groups/group-details.tsx +8 -142
  130. package/src/screens/groups/group-invite-panel.tsx +2 -7
  131. package/src/screens/groups/group-list.test.tsx +44 -0
  132. package/src/screens/groups/group-list.tsx +227 -288
  133. package/src/screens/groups/group-members.tsx +3 -2
  134. package/src/screens/groups/index.ts +0 -5
  135. package/src/screens/identity/identity-routes.test.ts +70 -0
  136. package/src/screens/identity/identity-routes.ts +301 -0
  137. package/src/screens/identity/identity-screen.test.tsx +138 -0
  138. package/src/screens/identity/identity-screen.tsx +282 -0
  139. package/src/screens/identity/index.ts +2 -0
  140. package/src/screens/identity/invite-contact-panel.tsx +45 -0
  141. package/src/screens/identity/profile-invite.tsx +93 -0
  142. package/src/screens/invites/accept-invite.tsx +1 -1
  143. package/src/screens/invites/activity-view.test.tsx +127 -0
  144. package/src/screens/invites/activity-view.tsx +162 -0
  145. package/src/screens/invites/index.ts +1 -4
  146. package/src/screens/invites/invite-capture.ts +0 -3
  147. package/src/screens/invites/invite-classify.test.ts +110 -0
  148. package/src/screens/invites/invites-ipc.ts +6 -55
  149. package/src/screens/invites/pending-invites-list.tsx +85 -61
  150. package/src/screens/network-viewer/network-viewer-ipc.ts +8 -82
  151. package/src/screens/search/global-search.tsx +2 -1
  152. package/src/screens/settings/settings-page.tsx +53 -126
  153. package/src/screens/setup-user.tsx +5 -6
  154. package/src/screens/tools/tool-access-ipc.ts +13 -74
  155. package/src/screens/welcome-modal.tsx +3 -3
  156. package/src/system-apps/identity.app.ts +9 -0
  157. package/src/system-apps/index.ts +3 -12
  158. package/src/tabs-layout/network-status-indicator.test.tsx +22 -0
  159. package/src/tabs-layout/network-status-indicator.tsx +31 -0
  160. package/src/tabs-layout/tabs-layout.tsx +7 -1
  161. package/src/tabs-layout/tabs-state.test.ts +152 -0
  162. package/src/tabs-layout/tabs-state.ts +300 -83
  163. package/dist/screens/invites/invites-screen.d.ts +0 -9
  164. package/dist/screens/invites/invites-screen.js +0 -36
  165. package/dist/system-apps/account.app.d.ts +0 -2
  166. package/dist/system-apps/account.app.js +0 -8
  167. package/dist/system-apps/contacts.app.d.ts +0 -2
  168. package/dist/system-apps/contacts.app.js +0 -9
  169. package/dist/system-apps/groups.app.d.ts +0 -2
  170. package/dist/system-apps/groups.app.js +0 -9
  171. package/dist/system-apps/invites.app.d.ts +0 -2
  172. package/dist/system-apps/invites.app.js +0 -9
  173. package/src/screens/invites/invites-screen.tsx +0 -78
  174. package/src/system-apps/account.app.ts +0 -7
  175. package/src/system-apps/contacts.app.ts +0 -8
  176. package/src/system-apps/groups.app.ts +0 -8
  177. package/src/system-apps/invites.app.ts +0 -8
@@ -14,8 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- // Import all contact screen components to ensure they register their routes
18
- require("./contact-list");
19
- require("./contact-details");
20
17
  __exportStar(require("./contact-details"), exports);
21
18
  __exportStar(require("./contact-list"), exports);
@@ -10,6 +10,7 @@ const peers_sdk_1 = require("@peers-app/peers-sdk");
10
10
  const react_1 = require("react");
11
11
  const react_qr_code_1 = __importDefault(require("react-qr-code"));
12
12
  const qr_scanner_1 = require("../../components/qr-scanner");
13
+ const identity_ipc_1 = require("./identity-ipc");
13
14
  const use_device_pairing_state_1 = require("./use-device-pairing-state");
14
15
  function isPairingCode(text) {
15
16
  try {
@@ -78,8 +79,8 @@ function usePairingSessionLifecycle(state) {
78
79
  mounted.current = false;
79
80
  const activeSessionId = sessionId.current;
80
81
  if (activeSessionId && !["idle", ...TERMINAL_PHASES].includes(phase.current)) {
81
- void peers_sdk_1.rpcServerCalls
82
- .devicePairingCancel({ sessionId: activeSessionId })
82
+ void (0, identity_ipc_1.identityTools)()
83
+ .then((tools) => tools.cancel({ sessionId: activeSessionId }))
83
84
  .catch(() => undefined);
84
85
  }
85
86
  };
@@ -95,11 +96,12 @@ function useCeremonyActions(lifecycle, onLeave, setLocalError) {
95
96
  }
96
97
  setApproving(true);
97
98
  try {
99
+ const tools = await (0, identity_ipc_1.identityTools)();
98
100
  if (action === "approve") {
99
- await peers_sdk_1.rpcServerCalls.devicePairingApprove({ sessionId });
101
+ await tools.approve({ sessionId });
100
102
  }
101
103
  else {
102
- await peers_sdk_1.rpcServerCalls.devicePairingReject({ sessionId });
104
+ await tools.reject({ sessionId });
103
105
  }
104
106
  }
105
107
  catch {
@@ -113,7 +115,9 @@ function useCeremonyActions(lifecycle, onLeave, setLocalError) {
113
115
  const sessionId = lifecycle.sessionId.current;
114
116
  lifecycle.phase.current = "idle";
115
117
  if (sessionId) {
116
- await peers_sdk_1.rpcServerCalls.devicePairingCancel({ sessionId }).catch(() => undefined);
118
+ await (0, identity_ipc_1.identityTools)()
119
+ .then((tools) => tools.cancel({ sessionId }))
120
+ .catch(() => undefined);
117
121
  }
118
122
  onLeave();
119
123
  };
@@ -138,13 +142,12 @@ function SourceDevicePairing(props) {
138
142
  setLocalError("");
139
143
  setStarting(true);
140
144
  try {
141
- const result = await peers_sdk_1.rpcServerCalls.devicePairingStartSource({ code: rawCode.trim() });
145
+ const tools = await (0, identity_ipc_1.identityTools)();
146
+ const result = await tools.startSource({ code: rawCode.trim() });
142
147
  lifecycle.sessionId.current = result.sessionId;
143
148
  lifecycle.phase.current = result.phase;
144
149
  if (!lifecycle.mounted.current && result.sessionId) {
145
- await peers_sdk_1.rpcServerCalls
146
- .devicePairingCancel({ sessionId: result.sessionId })
147
- .catch(() => undefined);
150
+ await tools.cancel({ sessionId: result.sessionId }).catch(() => undefined);
148
151
  }
149
152
  }
150
153
  catch {
@@ -189,7 +192,7 @@ const DESTINATION_PROGRESS_MESSAGES = {
189
192
  };
190
193
  function ShowingCode(props) {
191
194
  const code = props.state.code ?? "";
192
- return ((0, jsx_runtime_1.jsxs)("div", { className: "text-center", style: { maxWidth: 480, margin: "0 auto" }, children: [(0, jsx_runtime_1.jsx)("h3", { children: "Sign in from another device" }), (0, jsx_runtime_1.jsxs)("p", { className: "text-muted", children: ["On a device already signed in to Peers, open Settings \u2192 ", (0, jsx_runtime_1.jsx)("strong", { children: "Add another device" }), " ", "and type this code or scan it."] }), props.state.notice ? (0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: props.state.notice }) : null, (0, jsx_runtime_1.jsx)("output", { className: "d-block fs-3 font-monospace text-primary mb-3", style: { letterSpacing: "0.04em", wordBreak: "break-word" }, "aria-label": "Pairing code", children: code }), (0, jsx_runtime_1.jsx)("div", { className: "d-inline-block bg-white rounded p-3 mb-3", children: (0, jsx_runtime_1.jsx)(react_qr_code_1.default, { value: code, size: 200, "aria-label": "Pairing code QR" }) }), (0, jsx_runtime_1.jsxs)("p", { className: "small text-muted", children: ["Expires in ", formatCountdown(props.state.expiresAt)] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2 justify-content-center", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-outline-primary", onClick: () => void (0, peers_sdk_1.copyToClipboard)(code), children: "Copy code" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-outline-secondary", onClick: props.onCancel, children: "Cancel" })] })] }));
195
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "text-center", style: { maxWidth: 480, margin: "0 auto" }, children: [(0, jsx_runtime_1.jsx)("h3", { children: "Sign in from another device" }), (0, jsx_runtime_1.jsxs)("p", { className: "text-muted", children: ["On a device already signed in to Peers, open", " ", (0, jsx_runtime_1.jsx)("strong", { children: "Identity \u2192 Devices \u2192 Add a device" }), " and type this code or scan it."] }), props.state.notice ? (0, jsx_runtime_1.jsx)("div", { className: "alert alert-warning", children: props.state.notice }) : null, (0, jsx_runtime_1.jsx)("output", { className: "d-block fs-3 font-monospace text-primary mb-3", style: { letterSpacing: "0.04em", wordBreak: "break-word" }, "aria-label": "Pairing code", children: code }), (0, jsx_runtime_1.jsx)("div", { className: "d-inline-block bg-white rounded p-3 mb-3", children: (0, jsx_runtime_1.jsx)(react_qr_code_1.default, { value: code, size: 200, "aria-label": "Pairing code QR" }) }), (0, jsx_runtime_1.jsxs)("p", { className: "small text-muted", children: ["Expires in ", formatCountdown(props.state.expiresAt)] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2 justify-content-center", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-outline-primary", onClick: () => void (0, peers_sdk_1.copyToClipboard)(code), children: "Copy code" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-outline-secondary", onClick: props.onCancel, children: "Cancel" })] })] }));
193
196
  }
194
197
  /** Signed-out destination flow: shows a code and QR, then installs credentials after approval. */
195
198
  function DestinationDevicePairing(props) {
@@ -204,15 +207,13 @@ function DestinationDevicePairing(props) {
204
207
  return;
205
208
  }
206
209
  started.current = true;
207
- void peers_sdk_1.rpcServerCalls
208
- .devicePairingStartDestination({ deviceType: detectDeviceType() })
209
- .then(async (result) => {
210
+ void (0, identity_ipc_1.identityTools)()
211
+ .then(async (tools) => {
212
+ const result = await tools.startDestination({ deviceType: detectDeviceType() });
210
213
  lifecycle.sessionId.current = result.sessionId;
211
214
  lifecycle.phase.current = result.phase;
212
215
  if (!lifecycle.mounted.current && result.sessionId) {
213
- await peers_sdk_1.rpcServerCalls
214
- .devicePairingCancel({ sessionId: result.sessionId })
215
- .catch(() => undefined);
216
+ await tools.cancel({ sessionId: result.sessionId }).catch(() => undefined);
216
217
  }
217
218
  })
218
219
  .catch(() => setLocalError("Could not reach the pairing service. Check your connection."));
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const jsx_runtime_1 = require("react/jsx-runtime");
4
- const peers_sdk_1 = require("@peers-app/peers-sdk");
5
4
  const react_1 = require("@testing-library/react");
6
5
  const device_pairing_1 = require("./device-pairing");
7
6
  const mockRpc = {
@@ -12,13 +11,9 @@ const mockRpc = {
12
11
  cancel: jest.fn(),
13
12
  };
14
13
  let mockPairingState = { phase: "idle" };
15
- peers_sdk_1.rpcServerCalls.devicePairingStartSource =
16
- mockRpc.startSource;
17
- peers_sdk_1.rpcServerCalls.devicePairingStartDestination =
18
- mockRpc.startDestination;
19
- peers_sdk_1.rpcServerCalls.devicePairingApprove = mockRpc.approve;
20
- peers_sdk_1.rpcServerCalls.devicePairingReject = mockRpc.reject;
21
- peers_sdk_1.rpcServerCalls.devicePairingCancel = mockRpc.cancel;
14
+ jest.mock("./identity-ipc", () => ({
15
+ identityTools: async () => mockRpc,
16
+ }));
22
17
  jest.mock("./use-device-pairing-state", () => ({
23
18
  useDevicePairingState: () => mockPairingState,
24
19
  }));
@@ -34,7 +29,7 @@ describe("device pairing UI lifecycle", () => {
34
29
  resolveStart = resolve;
35
30
  }));
36
31
  const view = (0, react_1.render)((0, jsx_runtime_1.jsx)(device_pairing_1.DestinationDevicePairing, { onBack: jest.fn() }));
37
- expect(mockRpc.startDestination).toHaveBeenCalledWith({ deviceType: "browser" });
32
+ await (0, react_1.waitFor)(() => expect(mockRpc.startDestination).toHaveBeenCalledWith({ deviceType: "browser" }));
38
33
  view.unmount();
39
34
  resolveStart({
40
35
  sessionId: "00mh0wlipkdbeaw8tests0001",
@@ -0,0 +1,11 @@
1
+ import { type IHostContractConsumer, type IIdentityConsumer } from "@peers-app/peers-sdk";
2
+ /**
3
+ * Cached local-host consumer for System Identity.
4
+ *
5
+ * Pairing and session tools resolve before sign-in, so this handle passes no data context.
6
+ */
7
+ export declare const identityContract: IHostContractConsumer<IIdentityConsumer>;
8
+ /** Return the typed local-host consumer for System Identity. */
9
+ export declare function getIdentityConsumer(): Promise<IIdentityConsumer>;
10
+ /** Shorthand for `(await getIdentityConsumer()).tools`. */
11
+ export declare function identityTools(): Promise<IIdentityConsumer["tools"]>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.identityContract = void 0;
4
+ exports.getIdentityConsumer = getIdentityConsumer;
5
+ exports.identityTools = identityTools;
6
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
7
+ /**
8
+ * Cached local-host consumer for System Identity.
9
+ *
10
+ * Pairing and session tools resolve before sign-in, so this handle passes no data context.
11
+ */
12
+ exports.identityContract = (0, peers_sdk_1.hostContractConsumer)((0, peers_sdk_1.getIdentityContractDefinition)());
13
+ /** Return the typed local-host consumer for System Identity. */
14
+ async function getIdentityConsumer() {
15
+ return exports.identityContract.get();
16
+ }
17
+ /** Shorthand for `(await getIdentityConsumer()).tools`. */
18
+ async function identityTools() {
19
+ return (await getIdentityConsumer()).tools;
20
+ }
@@ -1,3 +1,3 @@
1
- import { type DevicePairingState } from "@peers-app/peers-sdk";
2
- /** Subscribes to renderer-safe host pairing state and loads its current snapshot. */
1
+ import type { DevicePairingState } from "@peers-app/peers-sdk";
2
+ /** Subscribe to the host pairing ceremony. Idle until the identity consumer is ready. */
3
3
  export declare function useDevicePairingState(): DevicePairingState;
@@ -1,31 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useDevicePairingState = useDevicePairingState;
4
- const peers_sdk_1 = require("@peers-app/peers-sdk");
5
- const react_1 = require("react");
4
+ const hooks_1 = require("../../hooks");
5
+ const use_host_contract_1 = require("../../hooks/use-host-contract");
6
+ const identity_ipc_1 = require("./identity-ipc");
6
7
  const IDLE_PAIRING_STATE = { phase: "idle" };
7
- /** Subscribes to renderer-safe host pairing state and loads its current snapshot. */
8
+ /** Subscribe to the host pairing ceremony. Idle until the identity consumer is ready. */
8
9
  function useDevicePairingState() {
9
- const [state, setState] = (0, react_1.useState)(IDLE_PAIRING_STATE);
10
- (0, react_1.useEffect)(() => {
11
- let active = true;
12
- const subscription = (0, peers_sdk_1.subscribe)(peers_sdk_1.DEVICE_PAIRING_STATE_EVENT, (event) => {
13
- if (active) {
14
- setState(peers_sdk_1.devicePairingStateSchema.parse(event.data));
15
- }
16
- });
17
- void peers_sdk_1.rpcServerCalls
18
- .devicePairingGetState()
19
- .then((value) => {
20
- if (active) {
21
- setState(peers_sdk_1.devicePairingStateSchema.parse(value));
22
- }
23
- })
24
- .catch(() => undefined);
25
- return () => {
26
- active = false;
27
- subscription.unsubscribe();
28
- };
29
- }, []);
30
- return state;
10
+ const { consumer } = (0, use_host_contract_1.useHostContract)(identity_ipc_1.identityContract);
11
+ const [state] = (0, hooks_1.useObservable)(consumer?.observables.state ?? IDLE_PAIRING_STATE);
12
+ return state ?? IDLE_PAIRING_STATE;
31
13
  }
@@ -0,0 +1,18 @@
1
+ import { type IDevice, type INetworkConnectionInfo } from "@peers-app/peers-sdk";
2
+ /** Compact, fully clickable row for one of the signed-in user's devices. */
3
+ export declare function DeviceRow(props: {
4
+ device: IDevice;
5
+ connection?: INetworkConnectionInfo;
6
+ isThisDevice: boolean;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ /** Which devices surface the Identity shell is showing. */
9
+ export type DevicesSectionMode = "list" | "pair" | "detail";
10
+ /**
11
+ * Devices section of Identity: the account's devices, pairing, or one device.
12
+ */
13
+ export declare function DevicesSection(props: {
14
+ mode?: DevicesSectionMode;
15
+ deviceId?: string;
16
+ }): import("react/jsx-runtime").JSX.Element;
17
+ /** Keep only devices whose signed-in owner is the requested user. */
18
+ export declare function devicesForUser(devices: IDevice[], userId: string): IDevice[];
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeviceRow = DeviceRow;
4
+ exports.DevicesSection = DevicesSection;
5
+ exports.devicesForUser = devicesForUser;
6
+ const jsx_runtime_1 = require("react/jsx-runtime");
7
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
8
+ const react_1 = require("react");
9
+ const input_1 = require("../../components/input");
10
+ const loading_indicator_1 = require("../../components/loading-indicator");
11
+ const globals_1 = require("../../globals");
12
+ const hooks_1 = require("../../hooks");
13
+ const device_pairing_1 = require("../device-pairing");
14
+ const identity_routes_1 = require("../identity/identity-routes");
15
+ const activity_view_1 = require("../invites/activity-view");
16
+ const network_viewer_ipc_1 = require("../network-viewer/network-viewer-ipc");
17
+ function trustLabel(level) {
18
+ return peers_sdk_1.TrustLevel[level] ?? String(level);
19
+ }
20
+ function connectionLabel(connection) {
21
+ if (!connection) {
22
+ return "Offline";
23
+ }
24
+ const path = connection.pathType === "direct"
25
+ ? "direct"
26
+ : connection.pathType === "indirect"
27
+ ? "indirect"
28
+ : "unknown path";
29
+ return `${connection.connectionState} · ${path}`;
30
+ }
31
+ /** Compact, fully clickable row for one of the signed-in user's devices. */
32
+ function DeviceRow(props) {
33
+ const connected = props.connection !== undefined;
34
+ return ((0, jsx_runtime_1.jsxs)("a", { href: `#${identity_routes_1.IDENTITY_DEVICES_PATH}/${props.device.deviceId}`, className: "list-group-item list-group-item-action d-flex align-items-center gap-3 py-3", children: [(0, jsx_runtime_1.jsx)("span", { className: "rounded-3 bg-body-secondary d-inline-flex align-items-center justify-content-center flex-shrink-0", style: { width: 44, height: 44 }, "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)("i", { className: `bi ${props.isThisDevice ? "bi-laptop-fill" : "bi-laptop"}` }) }), (0, jsx_runtime_1.jsxs)("span", { className: "flex-grow-1 overflow-hidden", children: [(0, jsx_runtime_1.jsxs)("span", { className: "d-flex align-items-center gap-2 flex-wrap", children: [(0, jsx_runtime_1.jsx)("span", { className: "fw-semibold text-truncate", children: props.device.name || props.device.reportedName || "Unnamed device" }), props.isThisDevice ? (0, jsx_runtime_1.jsx)("span", { className: "badge text-bg-primary", children: "This device" }) : null] }), (0, jsx_runtime_1.jsx)("span", { className: "small text-muted d-block", children: props.device.lastSeen ? `Seen ${relativeTime(props.device.lastSeen)}` : "Never seen" })] }), (0, jsx_runtime_1.jsx)("span", { className: `badge ${connected ? "text-bg-success" : "text-bg-secondary"}`, children: connected ? "Online" : "Offline" }), connected && props.connection?.pathType ? ((0, jsx_runtime_1.jsx)("span", { className: "badge text-bg-light border text-capitalize", children: props.connection.pathType })) : null, (0, jsx_runtime_1.jsx)("i", { className: "bi bi-chevron-right text-muted", "aria-hidden": "true" })] }));
35
+ }
36
+ function ThisDeviceCard(props) {
37
+ const name = (0, hooks_1.useObservableState)(props.device.name ?? "");
38
+ const [nameValue] = (0, hooks_1.useObservable)(name);
39
+ const [saving, setSaving] = (0, react_1.useState)(false);
40
+ (0, react_1.useEffect)(() => {
41
+ name(props.device.name ?? "");
42
+ }, [name, props.device.deviceId, props.device.name]);
43
+ const save = async () => {
44
+ setSaving(true);
45
+ try {
46
+ const userContext = await (0, peers_sdk_1.getUserContext)();
47
+ const table = (0, peers_sdk_1.Devices)(userContext.userDataContext);
48
+ const next = { ...props.device, name: nameValue.trim() || undefined };
49
+ await table.save(next);
50
+ }
51
+ catch (err) {
52
+ alert(`Could not save this device: ${err.message}`);
53
+ }
54
+ finally {
55
+ setSaving(false);
56
+ }
57
+ };
58
+ const dirty = (nameValue ?? "") !== (props.device.name ?? "");
59
+ return ((0, jsx_runtime_1.jsx)("div", { className: "card mb-3", children: (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsx)("h6", { className: "card-title", children: "This device" }), (0, jsx_runtime_1.jsx)("label", { className: "form-label small", htmlFor: "this-device-name", children: "Name" }), (0, jsx_runtime_1.jsxs)("div", { className: "input-group", children: [(0, jsx_runtime_1.jsx)(input_1.Input, { id: "this-device-name", value: name, className: "form-control", maxLength: peers_sdk_1.DISPLAY_NAME_MAX_LENGTH, placeholder: "e.g. My laptop" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-primary", disabled: !dirty || saving, onClick: () => void save(), children: "Save" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "small text-muted mt-2", children: [trustLabel(props.device.trustLevel), " \u00B7 ", props.device.deviceId] })] }) }));
60
+ }
61
+ /**
62
+ * Devices section of Identity: the account's devices, pairing, or one device.
63
+ */
64
+ function DevicesSection(props) {
65
+ const mode = props.mode ?? "list";
66
+ const userContext = (0, hooks_1.usePromise)(peers_sdk_1.getUserContext);
67
+ const [thisId] = (0, hooks_1.useObservable)(peers_sdk_1.thisDeviceId);
68
+ const [devices, setDevices] = (0, react_1.useState)();
69
+ const [connections, setConnections] = (0, react_1.useState)([]);
70
+ (0, react_1.useEffect)(() => {
71
+ if (!userContext) {
72
+ return;
73
+ }
74
+ let disposed = false;
75
+ const table = (0, peers_sdk_1.Devices)(userContext.userDataContext);
76
+ const load = async () => {
77
+ const rows = await table.list();
78
+ if (!disposed) {
79
+ setDevices(rows);
80
+ }
81
+ };
82
+ void load();
83
+ const subscription = table.dataChanged.subscribe(() => {
84
+ void load();
85
+ });
86
+ return () => {
87
+ disposed = true;
88
+ subscription.unsubscribe();
89
+ };
90
+ }, [userContext]);
91
+ (0, react_1.useEffect)(() => {
92
+ let disposed = false;
93
+ const load = async () => {
94
+ try {
95
+ const diagnostics = await (0, network_viewer_ipc_1.getNetworkDiagnosticsConsumer)();
96
+ const result = await diagnostics.tools.getConnections();
97
+ if (!disposed) {
98
+ setConnections(result.connections);
99
+ }
100
+ }
101
+ catch {
102
+ if (!disposed) {
103
+ setConnections([]);
104
+ }
105
+ }
106
+ };
107
+ void load();
108
+ const timer = window.setInterval(() => void load(), 5000);
109
+ return () => {
110
+ disposed = true;
111
+ window.clearInterval(timer);
112
+ };
113
+ }, []);
114
+ if (!userContext || !devices) {
115
+ return (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {});
116
+ }
117
+ const byId = new Map(connections.map((connection) => [connection.deviceId, connection]));
118
+ const accountDevices = devicesForUser(devices, userContext.userId);
119
+ const mine = accountDevices.find((device) => device.deviceId === thisId);
120
+ const others = accountDevices
121
+ .filter((device) => device.deviceId !== thisId)
122
+ .sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
123
+ if (mode === "pair") {
124
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", style: { maxWidth: 720 }, children: [(0, jsx_runtime_1.jsx)("a", { href: `#${identity_routes_1.IDENTITY_DEVICES_PATH}`, className: "btn btn-link btn-sm px-0 mb-2", children: "Back to devices" }), (0, jsx_runtime_1.jsx)(device_pairing_1.SourceDevicePairing, { onClose: () => (0, globals_1.mainContentPath)(identity_routes_1.IDENTITY_DEVICES_PATH) })] }));
125
+ }
126
+ if (mode === "detail") {
127
+ const device = accountDevices.find((entry) => entry.deviceId === props.deviceId);
128
+ if (!device) {
129
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", children: [(0, jsx_runtime_1.jsx)("a", { href: `#${identity_routes_1.IDENTITY_DEVICES_PATH}`, children: "Back to devices" }), (0, jsx_runtime_1.jsx)("p", { className: "mt-3 text-muted", children: "That device is not on this account." })] }));
130
+ }
131
+ return ((0, jsx_runtime_1.jsx)(DeviceDetail, { device: device, connection: byId.get(device.deviceId), isThisDevice: device.deviceId === thisId }));
132
+ }
133
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-xxl p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mb-3", children: [(0, jsx_runtime_1.jsxs)("h4", { className: "mb-1", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-laptop me-2" }), "My devices"] }), (0, jsx_runtime_1.jsxs)("div", { className: "small text-muted", children: ["Installations signed in to this account. Connection diagnostics stay in", " ", (0, jsx_runtime_1.jsx)("a", { href: "#network-viewer", children: "Network Viewer" }), "."] })] }), (0, jsx_runtime_1.jsx)(activity_view_1.PendingWorkLink, { type: "device" }), (0, jsx_runtime_1.jsxs)("div", { className: "list-group", children: [mine ? ((0, jsx_runtime_1.jsx)(DeviceRow, { device: mine, connection: byId.get(mine.deviceId), isThisDevice: true })) : null, others.map((device) => ((0, jsx_runtime_1.jsx)(DeviceRow, { device: device, connection: byId.get(device.deviceId), isThisDevice: false }, device.deviceId)))] }), !mine && others.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: "border rounded p-4 text-center", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-laptop fs-2 text-muted" }), (0, jsx_runtime_1.jsx)("h6", { className: "mt-2", children: "No devices are registered yet" }), (0, jsx_runtime_1.jsx)("a", { href: `#${identity_routes_1.IDENTITY_PAIR_PATH}`, className: "btn btn-primary btn-sm", children: "Add a device" })] })) : null, mine && others.length === 0 ? ((0, jsx_runtime_1.jsxs)("p", { className: "small text-muted mt-3", children: ["This is your only device. Use ", (0, jsx_runtime_1.jsx)("a", { href: `#${identity_routes_1.IDENTITY_PAIR_PATH}`, children: "Add device" }), " to sign in somewhere else."] })) : null] }));
134
+ }
135
+ function DeviceDetail(props) {
136
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-xxl p-3", children: [(0, jsx_runtime_1.jsx)("a", { href: `#${identity_routes_1.IDENTITY_DEVICES_PATH}`, className: "btn btn-link btn-sm px-0 mb-2", children: "Back to devices" }), (0, jsx_runtime_1.jsx)("h4", { className: "mb-3", children: props.device.name || "Unnamed device" }), props.isThisDevice ? (0, jsx_runtime_1.jsx)(ThisDeviceCard, { device: props.device }) : null, (0, jsx_runtime_1.jsxs)("dl", { className: "row small mb-3", children: [(0, jsx_runtime_1.jsx)("dt", { className: "col-sm-4", children: "Trust" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-sm-8", children: trustLabel(props.device.trustLevel) }), (0, jsx_runtime_1.jsx)("dt", { className: "col-sm-4", children: "Connection" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-sm-8", children: connectionLabel(props.connection) }), (0, jsx_runtime_1.jsx)("dt", { className: "col-sm-4", children: "Last seen" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-sm-8", children: props.device.lastSeen ? new Date(props.device.lastSeen).toLocaleString() : "Unknown" }), (0, jsx_runtime_1.jsx)("dt", { className: "col-sm-4", children: "Device ID" }), (0, jsx_runtime_1.jsx)("dd", { className: "col-sm-8 text-break", children: props.device.deviceId })] }), (0, jsx_runtime_1.jsx)("a", { href: "#network-viewer", className: "btn btn-outline-secondary btn-sm me-2", children: "Open Network Viewer" }), props.isThisDevice ? null : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "btn btn-outline-danger btn-sm", onClick: () => void forgetDevice(props.device), children: "Forget" }))] }));
137
+ }
138
+ async function forgetDevice(device) {
139
+ const confirmed = confirm(`Forget ${device.name || device.deviceId}?`);
140
+ if (!confirmed) {
141
+ return;
142
+ }
143
+ const control = await (0, network_viewer_ipc_1.getNetworkControlConsumer)();
144
+ const result = await control.tools.deleteDevice({ deviceId: device.deviceId });
145
+ if (!result.success) {
146
+ alert(result.message);
147
+ return;
148
+ }
149
+ (0, globals_1.mainContentPath)(identity_routes_1.IDENTITY_DEVICES_PATH);
150
+ }
151
+ /** Keep only devices whose signed-in owner is the requested user. */
152
+ function devicesForUser(devices, userId) {
153
+ return devices.filter((device) => device.userId === userId);
154
+ }
155
+ function relativeTime(value) {
156
+ const elapsedMs = Math.max(0, Date.now() - new Date(value).getTime());
157
+ const minutes = Math.floor(elapsedMs / 60_000);
158
+ if (minutes < 1) {
159
+ return "just now";
160
+ }
161
+ if (minutes < 60) {
162
+ return `${minutes}m ago`;
163
+ }
164
+ const hours = Math.floor(minutes / 60);
165
+ if (hours < 24) {
166
+ return `${hours}h ago`;
167
+ }
168
+ const days = Math.floor(hours / 24);
169
+ if (days < 30) {
170
+ return `${days}d ago`;
171
+ }
172
+ return new Date(value).toLocaleDateString();
173
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
+ const react_1 = require("@testing-library/react");
6
+ const devices_list_1 = require("./devices-list");
7
+ jest.mock("../../globals", () => ({
8
+ mainContentPath: jest.fn(),
9
+ }));
10
+ jest.mock("../../hooks", () => ({
11
+ useObservable: jest.fn(),
12
+ useObservableState: jest.fn(),
13
+ usePromise: jest.fn(),
14
+ }));
15
+ jest.mock("../device-pairing", () => ({
16
+ SourceDevicePairing: () => null,
17
+ }));
18
+ jest.mock("../invites/activity-view", () => ({
19
+ PendingWorkLink: () => null,
20
+ }));
21
+ jest.mock("../network-viewer/network-viewer-ipc", () => ({
22
+ getNetworkControlConsumer: jest.fn(),
23
+ getNetworkDiagnosticsConsumer: jest.fn(),
24
+ }));
25
+ function device(deviceId, userId, name) {
26
+ return {
27
+ deviceId,
28
+ userId,
29
+ name,
30
+ firstSeen: new Date("2026-01-01T00:00:00Z"),
31
+ lastSeen: new Date(),
32
+ trustLevel: peers_sdk_1.TrustLevel.Trusted,
33
+ };
34
+ }
35
+ describe("Devices identity list", () => {
36
+ it("filters out devices owned by other users", () => {
37
+ const mine = device("abcdefghijklmnopqrstuvwxy", "mine000000000000000000001", "Laptop");
38
+ const theirs = device("zyxwvutsrqponmlkjihgfedcb", "theirs000000000000000001", "Other");
39
+ expect((0, devices_list_1.devicesForUser)([mine, theirs], mine.userId)).toEqual([mine]);
40
+ });
41
+ it("shows the current-device and connection context without a destructive row action", () => {
42
+ const mine = device("abcdefghijklmnopqrstuvwxy", "mine000000000000000000001", "Laptop");
43
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(devices_list_1.DeviceRow, { device: mine, isThisDevice: true, connection: {
44
+ deviceId: mine.deviceId,
45
+ connectionState: "connected",
46
+ pathType: "direct",
47
+ } }));
48
+ expect(react_1.screen.getByText("Laptop")).toBeInTheDocument();
49
+ expect(react_1.screen.getByText("This device")).toBeInTheDocument();
50
+ expect(react_1.screen.getByText("Online")).toBeInTheDocument();
51
+ expect(react_1.screen.queryByRole("button", { name: /Forget/i })).not.toBeInTheDocument();
52
+ });
53
+ });
@@ -0,0 +1 @@
1
+ export { DevicesSection } from "./devices-list";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DevicesSection = void 0;
4
+ var devices_list_1 = require("./devices-list");
5
+ Object.defineProperty(exports, "DevicesSection", { enumerable: true, get: function () { return devices_list_1.DevicesSection; } });
@@ -1,5 +1,6 @@
1
1
  interface IProps {
2
2
  groupId: string;
3
3
  }
4
+ /** Edit group metadata and manage members and invitations inside Identity. */
4
5
  export declare const GroupDetails: (props: IProps) => import("react/jsx-runtime").JSX.Element;
5
6
  export {};
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GroupDetails = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const peers_sdk_1 = require("@peers-app/peers-sdk");
6
- const react_1 = require("react");
7
6
  const checkbox_1 = require("../../components/checkbox");
8
7
  const input_1 = require("../../components/input");
9
8
  const loading_indicator_1 = require("../../components/loading-indicator");
@@ -11,9 +10,8 @@ const editor_inline_1 = require("../../components/markdown-editor/editor-inline"
11
10
  const save_button_1 = require("../../components/save-button");
12
11
  const tabs_1 = require("../../components/tabs");
13
12
  const hooks_1 = require("../../hooks");
14
- const tabs_state_1 = require("../../tabs-layout/tabs-state");
15
- const ui_loader_1 = require("../../ui-router/ui-loader");
16
13
  const group_members_1 = require("./group-members");
14
+ /** Edit group metadata and manage members and invitations inside Identity. */
17
15
  const GroupDetails = (props) => {
18
16
  const group = (0, hooks_1.usePromise)(async () => {
19
17
  const userContext = await (0, peers_sdk_1.getUserContext)();
@@ -22,7 +20,6 @@ const GroupDetails = (props) => {
22
20
  if (!group) {
23
21
  throw new Error("Group not found");
24
22
  }
25
- (0, tabs_state_1.updateActiveTabTitle)(group.name || "Group");
26
23
  const groupDoc = userGroupTable.initDoc(group);
27
24
  // saves go to both the group and user db
28
25
  groupDoc.save = async () => {
@@ -47,14 +44,14 @@ const GroupDetails = (props) => {
47
44
  if (!group) {
48
45
  return (0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {});
49
46
  }
50
- return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex", children: [(0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)("i", { className: `${(0, peers_sdk_1.getIconClassName)((0, peers_sdk_1.Groups)())} me-2` }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1", children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)(input_1.Input, { className: "border border-0", style: { width: "100%", outline: "none", backgroundColor: "transparent" }, value: group.qs.name }, group.groupId) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex gap-2", children: [(0, jsx_runtime_1.jsx)(ShareButton, { groupId: props.groupId }), (0, jsx_runtime_1.jsx)(save_button_1.SaveButton, { doc: group }, group.groupId)] })] }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
47
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "container-xxl p-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "d-flex", children: [(0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)("i", { className: `${(0, peers_sdk_1.getIconClassName)((0, peers_sdk_1.Groups)())} me-2` }) }) }), (0, jsx_runtime_1.jsx)("div", { className: "flex-grow-1", children: (0, jsx_runtime_1.jsx)("h4", { children: (0, jsx_runtime_1.jsx)(input_1.Input, { className: "border border-0", style: { width: "100%", outline: "none", backgroundColor: "transparent" }, value: group.qs.name }, group.groupId) }) }), (0, jsx_runtime_1.jsx)(save_button_1.SaveButton, { doc: group }, group.groupId)] }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { tabs: [
51
48
  {
52
49
  name: "Info",
53
- content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(GroupInfo, { group: group }) })),
50
+ content: ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(GroupInfo, { group: group }) })),
54
51
  },
55
52
  {
56
53
  name: "Members",
57
- content: ((0, jsx_runtime_1.jsx)(tabs_1.ScreenTabBody, { children: (0, jsx_runtime_1.jsx)(GroupMembersWrapper, { groupId: props.groupId }) })),
54
+ content: ((0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(GroupMembersWrapper, { groupId: props.groupId }) })),
58
55
  },
59
56
  ] }, group.groupId)] }));
60
57
  };
@@ -64,65 +61,9 @@ const GroupInfo = (props) => {
64
61
  group.description = group.description || "";
65
62
  return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("small", { children: "Name:" }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: group.qs.name, className: "form-control mb-3 p-0 ps-2", placeholder: "Group name", title: "Group name" }), (0, jsx_runtime_1.jsx)("small", { children: "Icon Class Name:" }), (0, jsx_runtime_1.jsx)(input_1.Input, { value: group.qs.iconClassName, className: "form-control mb-3 p-0 ps-2", placeholder: "bi-people-fill", title: "Icon class name (e.g., bi-people-fill, bi-house-fill, bi-star-fill)" }), (0, jsx_runtime_1.jsx)("small", { children: "Group ID:" }), (0, jsx_runtime_1.jsx)("div", { className: "form-control mb-3 p-2 bg-body-secondary text-body-secondary", style: { fontFamily: "monospace", fontSize: "0.9em" }, children: group.groupId }), (0, jsx_runtime_1.jsx)("div", { className: "d-flex flex-row", children: (0, jsx_runtime_1.jsxs)("div", { className: "ms-2", children: [(0, jsx_runtime_1.jsx)("label", { className: "form-label small mt-2 me-2", children: "Disabled:" }), (0, jsx_runtime_1.jsx)(checkbox_1.Checkbox, { className: "form-check-input mt-2", checked: group.qs.disabled })] }) }), (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("small", { className: "mt-2", children: "Description:" }), (0, jsx_runtime_1.jsx)(editor_inline_1.MarkdownEditorInline, { value: group.qs.description })] }));
66
63
  };
67
- const ShareButton = ({ groupId }) => {
68
- const [isSharing, setIsSharing] = (0, react_1.useState)(false);
69
- const [shareResult, setShareResult] = (0, react_1.useState)(null);
70
- const handleShare = async () => {
71
- setIsSharing(true);
72
- setShareResult(null);
73
- try {
74
- // Generate the group share data
75
- const groupShare = await (0, peers_sdk_1.generateGroupShare)(groupId);
76
- // Convert to JSON
77
- const groupShareJson = JSON.stringify(groupShare, null, 2);
78
- // Copy to clipboard
79
- const copied = await (0, peers_sdk_1.copyToClipboard)(groupShareJson);
80
- if (copied) {
81
- setShareResult({
82
- success: true,
83
- message: `Group share data copied to clipboard! (${groupShareJson.length} chars, ${groupShare.groupMembers.length} admin members)`,
84
- });
85
- }
86
- else {
87
- setShareResult({
88
- success: false,
89
- message: "Failed to copy to clipboard. Please copy manually from the modal.",
90
- });
91
- }
92
- }
93
- catch (error) {
94
- setShareResult({
95
- success: false,
96
- message: `Error generating share: ${error instanceof Error ? error.message : String(error)}`,
97
- });
98
- }
99
- finally {
100
- setIsSharing(false);
101
- }
102
- };
103
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { className: "btn btn-secondary btn-sm", onClick: handleShare, disabled: isSharing, title: "Share group with admin members", children: isSharing ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "spinner-border spinner-border-sm me-1", role: "status", "aria-hidden": "true" }), "Sharing..."] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("i", { className: "bi-share me-1" }), "Share"] })) }), shareResult && ((0, jsx_runtime_1.jsx)("div", { className: "position-fixed top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center", style: { backgroundColor: "rgba(0,0,0,0.5)", zIndex: 1050 }, children: (0, jsx_runtime_1.jsxs)("div", { className: "card", style: { maxWidth: "500px", width: "90%" }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex justify-content-between align-items-center", children: [(0, jsx_runtime_1.jsxs)("h6", { className: "mb-0", children: [(0, jsx_runtime_1.jsx)("i", { className: `bi-${shareResult.success ? "check-circle text-success" : "exclamation-triangle text-warning"} me-2` }), "Group Share ", shareResult.success ? "Ready" : "Error"] }), (0, jsx_runtime_1.jsx)("button", { className: "btn-close", onClick: () => setShareResult(null), "aria-label": "Close" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "card-body", children: [(0, jsx_runtime_1.jsx)("p", { className: "mb-3", children: shareResult.message }), (0, jsx_runtime_1.jsxs)("div", { className: "text-end", children: [(0, jsx_runtime_1.jsx)("button", { className: "btn btn-secondary me-2", onClick: () => setShareResult(null), children: "Close" }), shareResult.success && ((0, jsx_runtime_1.jsx)("button", { className: "btn btn-primary", onClick: handleShare, children: "Copy Again" }))] })] })] }) }))] }));
104
- };
105
64
  const GroupMembersWrapper = (props) => {
106
65
  const userContext = (0, hooks_1.usePromise)(() => (0, peers_sdk_1.getUserContext)());
107
66
  if (!userContext)
108
67
  return null;
109
68
  return (0, jsx_runtime_1.jsx)(group_members_1.GroupMembersUI, { groupId: props.groupId, userContext: userContext });
110
69
  };
111
- (0, ui_loader_1.registerInternalPeersUI)({
112
- peersUIId: "00group00details00component",
113
- component: exports.GroupDetails,
114
- routes: [
115
- {
116
- isMatch: (props, context) => {
117
- const match = context.path.match(/^groups\/([a-zA-Z0-9]{25})$/);
118
- if (match) {
119
- props.groupId = match[1];
120
- return true;
121
- }
122
- return false;
123
- },
124
- uiCategory: "screen",
125
- priority: 2,
126
- },
127
- ],
128
- });
@@ -5,9 +5,9 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const peers_sdk_1 = require("@peers-app/peers-sdk");
6
6
  const react_1 = require("react");
7
7
  const typeahead_1 = require("../../components/typeahead");
8
+ const activity_view_1 = require("../invites/activity-view");
8
9
  const invite_share_1 = require("../invites/invite-share");
9
10
  const invites_ipc_1 = require("../invites/invites-ipc");
10
- const pending_invites_list_1 = require("../invites/pending-invites-list");
11
11
  const ROLE_OPTIONS = [
12
12
  { value: peers_sdk_1.GroupMemberRole.Reader, label: "Reader" },
13
13
  { value: peers_sdk_1.GroupMemberRole.Writer, label: "Writer" },
@@ -121,5 +121,5 @@ function ShareLinkCard(props) {
121
121
  */
122
122
  function GroupInvitePanel(props) {
123
123
  const [tab, setTab] = (0, react_1.useState)("contacts");
124
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex align-items-center gap-3", children: [(0, jsx_runtime_1.jsx)("h6", { className: "mb-0", children: "Invite people" }), (0, jsx_runtime_1.jsxs)("div", { className: "btn-group btn-group-sm ms-auto", role: "group", "aria-label": "Invite method", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn ${tab === "contacts" ? "btn-primary" : "btn-outline-primary"}`, onClick: () => setTab("contacts"), children: "Contacts" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn ${tab === "link" ? "btn-primary" : "btn-outline-primary"}`, onClick: () => setTab("link"), children: "Share a link" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "card-body", children: tab === "contacts" ? (0, jsx_runtime_1.jsx)(InviteContactsCard, { ...props }) : (0, jsx_runtime_1.jsx)(ShareLinkCard, { ...props }) })] }), (0, jsx_runtime_1.jsx)(pending_invites_list_1.PendingInvitesList, { type: "group", groupId: props.groupId, hideWhenEmpty: true, title: "Pending invites and join requests" })] }));
124
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "card mb-3", children: [(0, jsx_runtime_1.jsxs)("div", { className: "card-header d-flex align-items-center gap-3", children: [(0, jsx_runtime_1.jsx)("h6", { className: "mb-0", children: "Invite people" }), (0, jsx_runtime_1.jsxs)("div", { className: "btn-group btn-group-sm ms-auto", role: "group", "aria-label": "Invite method", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn ${tab === "contacts" ? "btn-primary" : "btn-outline-primary"}`, onClick: () => setTab("contacts"), children: "Contacts" }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: `btn ${tab === "link" ? "btn-primary" : "btn-outline-primary"}`, onClick: () => setTab("link"), children: "Share a link" })] })] }), (0, jsx_runtime_1.jsx)("div", { className: "card-body", children: tab === "contacts" ? (0, jsx_runtime_1.jsx)(InviteContactsCard, { ...props }) : (0, jsx_runtime_1.jsx)(ShareLinkCard, { ...props }) })] }), (0, jsx_runtime_1.jsx)(activity_view_1.PendingWorkLink, { type: "group", groupId: props.groupId })] }));
125
125
  }