@peers-app/peers-ui 0.25.2 → 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.
- package/README.md +2 -1
- package/dist/components/markdown-editor/mention-node.d.ts +4 -0
- package/dist/components/markdown-editor/mention-node.js +4 -1
- package/dist/components/responsive-section-nav.d.ts +34 -0
- package/dist/components/responsive-section-nav.js +19 -0
- package/dist/components/responsive-section-nav.test.d.ts +1 -0
- package/dist/components/responsive-section-nav.test.js +45 -0
- package/dist/components/router.d.ts +2 -3
- package/dist/components/router.js +6 -9
- package/dist/globals-hash.test.d.ts +1 -0
- package/dist/globals-hash.test.js +16 -0
- package/dist/globals.d.ts +19 -0
- package/dist/globals.js +51 -12
- package/dist/hooks/use-host-contract.d.ts +18 -0
- package/dist/hooks/use-host-contract.js +53 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/peers-services-registration.d.ts +1 -5
- package/dist/peers-services-registration.js +6 -16
- package/dist/root-layout/root-layout-host.js +7 -4
- package/dist/root-layout/root-layout-host.test.js +3 -6
- package/dist/screens/account/account-screen.d.ts +1 -0
- package/dist/screens/account/account-screen.js +29 -53
- package/dist/screens/account/account-screen.test.d.ts +1 -0
- package/dist/screens/account/account-screen.test.js +47 -0
- package/dist/screens/contacts/contact-details.js +2 -23
- package/dist/screens/contacts/contact-list.d.ts +24 -1
- package/dist/screens/contacts/contact-list.js +128 -143
- package/dist/screens/contacts/contact-list.test.d.ts +1 -0
- package/dist/screens/contacts/contact-list.test.js +48 -0
- package/dist/screens/contacts/index.d.ts +0 -2
- package/dist/screens/contacts/index.js +0 -3
- package/dist/screens/device-pairing/device-pairing.js +17 -16
- package/dist/screens/device-pairing/device-pairing.test.js +4 -9
- package/dist/screens/device-pairing/identity-ipc.d.ts +11 -0
- package/dist/screens/device-pairing/identity-ipc.js +20 -0
- package/dist/screens/device-pairing/use-device-pairing-state.d.ts +2 -2
- package/dist/screens/device-pairing/use-device-pairing-state.js +7 -25
- package/dist/screens/devices/devices-list.d.ts +18 -0
- package/dist/screens/devices/devices-list.js +173 -0
- package/dist/screens/devices/devices-list.test.d.ts +1 -0
- package/dist/screens/devices/devices-list.test.js +53 -0
- package/dist/screens/devices/index.d.ts +1 -0
- package/dist/screens/devices/index.js +5 -0
- package/dist/screens/groups/group-details.d.ts +1 -0
- package/dist/screens/groups/group-details.js +4 -63
- package/dist/screens/groups/group-invite-panel.js +2 -2
- package/dist/screens/groups/group-list.d.ts +18 -1
- package/dist/screens/groups/group-list.js +107 -136
- package/dist/screens/groups/group-list.test.d.ts +1 -0
- package/dist/screens/groups/group-list.test.js +38 -0
- package/dist/screens/groups/group-members.d.ts +1 -0
- package/dist/screens/groups/group-members.js +2 -1
- package/dist/screens/groups/index.d.ts +0 -3
- package/dist/screens/groups/index.js +0 -4
- package/dist/screens/identity/identity-routes.d.ts +107 -0
- package/dist/screens/identity/identity-routes.js +245 -0
- package/dist/screens/identity/identity-routes.test.d.ts +1 -0
- package/dist/screens/identity/identity-routes.test.js +51 -0
- package/dist/screens/identity/identity-screen.d.ts +9 -0
- package/dist/screens/identity/identity-screen.js +143 -0
- package/dist/screens/identity/identity-screen.test.d.ts +1 -0
- package/dist/screens/identity/identity-screen.test.js +103 -0
- package/dist/screens/identity/index.d.ts +2 -0
- package/dist/screens/identity/index.js +6 -0
- package/dist/screens/identity/invite-contact-panel.d.ts +4 -0
- package/dist/screens/identity/invite-contact-panel.js +30 -0
- package/dist/screens/identity/profile-invite.d.ts +7 -0
- package/dist/screens/identity/profile-invite.js +62 -0
- package/dist/screens/invites/accept-invite.js +1 -1
- package/dist/screens/invites/activity-view.d.ts +24 -0
- package/dist/screens/invites/activity-view.js +64 -0
- package/dist/screens/invites/activity-view.test.d.ts +1 -0
- package/dist/screens/invites/activity-view.test.js +108 -0
- package/dist/screens/invites/index.d.ts +1 -2
- package/dist/screens/invites/index.js +1 -3
- package/dist/screens/invites/invite-capture.d.ts +0 -2
- package/dist/screens/invites/invite-capture.js +1 -3
- package/dist/screens/invites/invite-classify.test.d.ts +1 -0
- package/dist/screens/invites/invite-classify.test.js +98 -0
- package/dist/screens/invites/invites-ipc.d.ts +1 -1
- package/dist/screens/invites/invites-ipc.js +5 -45
- package/dist/screens/invites/pending-invites-list.d.ts +35 -16
- package/dist/screens/invites/pending-invites-list.js +69 -30
- package/dist/screens/network-viewer/network-viewer-ipc.js +4 -59
- package/dist/screens/search/global-search.d.ts +1 -0
- package/dist/screens/search/global-search.js +2 -1
- package/dist/screens/settings/settings-page.js +25 -58
- package/dist/screens/setup-user.js +5 -5
- package/dist/screens/tools/tool-access-ipc.d.ts +2 -0
- package/dist/screens/tools/tool-access-ipc.js +8 -55
- package/dist/screens/welcome-modal.js +3 -2
- package/dist/system-apps/identity.app.d.ts +3 -0
- package/dist/system-apps/identity.app.js +10 -0
- package/dist/system-apps/index.d.ts +1 -4
- package/dist/system-apps/index.js +5 -17
- package/dist/tabs-layout/network-status-indicator.js +10 -3
- package/dist/tabs-layout/network-status-indicator.test.js +15 -0
- package/dist/tabs-layout/tabs-layout.js +3 -1
- package/dist/tabs-layout/tabs-state.d.ts +79 -1
- package/dist/tabs-layout/tabs-state.js +256 -79
- package/dist/tabs-layout/tabs-state.test.d.ts +1 -0
- package/dist/tabs-layout/tabs-state.test.js +110 -0
- package/package.json +3 -3
- package/src/components/markdown-editor/mention-node.ts +5 -1
- package/src/components/responsive-section-nav.test.tsx +88 -0
- package/src/components/responsive-section-nav.tsx +92 -0
- package/src/components/router.tsx +6 -9
- package/src/globals-hash.test.ts +19 -0
- package/src/globals.tsx +53 -13
- package/src/hooks/use-host-contract.ts +75 -0
- package/src/index.tsx +1 -0
- package/src/peers-services-registration.ts +6 -17
- package/src/root-layout/root-layout-host.test.tsx +4 -7
- package/src/root-layout/root-layout-host.tsx +9 -11
- package/src/screens/account/account-screen.test.tsx +61 -0
- package/src/screens/account/account-screen.tsx +116 -95
- package/src/screens/contacts/contact-details.tsx +4 -26
- package/src/screens/contacts/contact-list.test.tsx +59 -0
- package/src/screens/contacts/contact-list.tsx +246 -260
- package/src/screens/contacts/index.ts +0 -4
- package/src/screens/device-pairing/device-pairing.test.tsx +7 -9
- package/src/screens/device-pairing/device-pairing.tsx +19 -23
- package/src/screens/device-pairing/identity-ipc.ts +25 -0
- package/src/screens/device-pairing/use-device-pairing-state.ts +10 -33
- package/src/screens/devices/devices-list.test.tsx +68 -0
- package/src/screens/devices/devices-list.tsx +361 -0
- package/src/screens/devices/index.ts +1 -0
- package/src/screens/groups/group-details.tsx +8 -142
- package/src/screens/groups/group-invite-panel.tsx +2 -7
- package/src/screens/groups/group-list.test.tsx +44 -0
- package/src/screens/groups/group-list.tsx +227 -288
- package/src/screens/groups/group-members.tsx +3 -2
- package/src/screens/groups/index.ts +0 -5
- package/src/screens/identity/identity-routes.test.ts +70 -0
- package/src/screens/identity/identity-routes.ts +301 -0
- package/src/screens/identity/identity-screen.test.tsx +138 -0
- package/src/screens/identity/identity-screen.tsx +282 -0
- package/src/screens/identity/index.ts +2 -0
- package/src/screens/identity/invite-contact-panel.tsx +45 -0
- package/src/screens/identity/profile-invite.tsx +93 -0
- package/src/screens/invites/accept-invite.tsx +1 -1
- package/src/screens/invites/activity-view.test.tsx +127 -0
- package/src/screens/invites/activity-view.tsx +162 -0
- package/src/screens/invites/index.ts +1 -4
- package/src/screens/invites/invite-capture.ts +0 -3
- package/src/screens/invites/invite-classify.test.ts +110 -0
- package/src/screens/invites/invites-ipc.ts +6 -55
- package/src/screens/invites/pending-invites-list.tsx +85 -61
- package/src/screens/network-viewer/network-viewer-ipc.ts +8 -82
- package/src/screens/search/global-search.tsx +2 -1
- package/src/screens/settings/settings-page.tsx +53 -126
- package/src/screens/setup-user.tsx +5 -6
- package/src/screens/tools/tool-access-ipc.ts +13 -74
- package/src/screens/welcome-modal.tsx +3 -3
- package/src/system-apps/identity.app.ts +9 -0
- package/src/system-apps/index.ts +3 -12
- package/src/tabs-layout/network-status-indicator.test.tsx +22 -0
- package/src/tabs-layout/network-status-indicator.tsx +31 -0
- package/src/tabs-layout/tabs-layout.tsx +7 -1
- package/src/tabs-layout/tabs-state.test.ts +152 -0
- package/src/tabs-layout/tabs-state.ts +300 -83
- package/dist/screens/invites/invites-screen.d.ts +0 -9
- package/dist/screens/invites/invites-screen.js +0 -36
- package/dist/system-apps/account.app.d.ts +0 -2
- package/dist/system-apps/account.app.js +0 -8
- package/dist/system-apps/contacts.app.d.ts +0 -2
- package/dist/system-apps/contacts.app.js +0 -9
- package/dist/system-apps/groups.app.d.ts +0 -2
- package/dist/system-apps/groups.app.js +0 -9
- package/dist/system-apps/invites.app.d.ts +0 -2
- package/dist/system-apps/invites.app.js +0 -9
- package/src/screens/invites/invites-screen.tsx +0 -78
- package/src/system-apps/account.app.ts +0 -7
- package/src/system-apps/contacts.app.ts +0 -8
- package/src/system-apps/groups.app.ts +0 -8
- package/src/system-apps/invites.app.ts +0 -8
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical Identity routes and legacy aliases.
|
|
4
|
+
*
|
|
5
|
+
* Contacts, Groups, Devices, Account, and the old Invites paths all render
|
|
6
|
+
* inside the Identity app. Navigation rewrites those aliases before a tab is
|
|
7
|
+
* updated.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.IDENTITY_ACCOUNT_PATH = exports.IDENTITY_PAIR_PATH = exports.IDENTITY_DEVICES_PATH = exports.IDENTITY_GROUP_NEW_PATH = exports.IDENTITY_GROUPS_PATH = exports.IDENTITY_PEOPLE_SHARE_PATH = exports.IDENTITY_PEOPLE_INVITE_PATH = exports.IDENTITY_PEOPLE_PATH = exports.IDENTITY_ACCEPT_PATH = exports.IDENTITY_ACTIVITY_PATH = exports.IDENTITY_APP_PATH = void 0;
|
|
11
|
+
exports.parseRoute = parseRoute;
|
|
12
|
+
exports.canonicalizeRoute = canonicalizeRoute;
|
|
13
|
+
exports.isIdentityPath = isIdentityPath;
|
|
14
|
+
exports.identitySectionFromPath = identitySectionFromPath;
|
|
15
|
+
exports.parseIdentityLocation = parseIdentityLocation;
|
|
16
|
+
exports.identityActivityFilterPath = identityActivityFilterPath;
|
|
17
|
+
/** Top-level Identity app path. Opening this lands on Activity. */
|
|
18
|
+
exports.IDENTITY_APP_PATH = "identity";
|
|
19
|
+
/** Unified invite inbox. */
|
|
20
|
+
exports.IDENTITY_ACTIVITY_PATH = "identity/activity";
|
|
21
|
+
/** Paste or scan an invite. */
|
|
22
|
+
exports.IDENTITY_ACCEPT_PATH = "identity/activity/accept";
|
|
23
|
+
/** People (contacts) list. */
|
|
24
|
+
exports.IDENTITY_PEOPLE_PATH = "identity/people";
|
|
25
|
+
/** Issue a one-off invitation to connect with a person. */
|
|
26
|
+
exports.IDENTITY_PEOPLE_INVITE_PATH = "identity/people/invite";
|
|
27
|
+
/** Show the reusable profile invitation. */
|
|
28
|
+
exports.IDENTITY_PEOPLE_SHARE_PATH = "identity/people/share";
|
|
29
|
+
/** Groups list. */
|
|
30
|
+
exports.IDENTITY_GROUPS_PATH = "identity/groups";
|
|
31
|
+
/** Create a group. */
|
|
32
|
+
exports.IDENTITY_GROUP_NEW_PATH = "identity/groups/new";
|
|
33
|
+
/** Devices list. */
|
|
34
|
+
exports.IDENTITY_DEVICES_PATH = "identity/devices";
|
|
35
|
+
/** Source-device pairing flow. */
|
|
36
|
+
exports.IDENTITY_PAIR_PATH = "identity/devices/pair";
|
|
37
|
+
/** Peers Services registration. */
|
|
38
|
+
exports.IDENTITY_ACCOUNT_PATH = "identity/account";
|
|
39
|
+
const IDENTITY_SECTIONS = new Set([
|
|
40
|
+
"activity",
|
|
41
|
+
"people",
|
|
42
|
+
"groups",
|
|
43
|
+
"devices",
|
|
44
|
+
"account",
|
|
45
|
+
]);
|
|
46
|
+
const INVITE_TYPES = new Set(["contact", "group", "device"]);
|
|
47
|
+
/**
|
|
48
|
+
* Split a hash path into its pathname and query.
|
|
49
|
+
*
|
|
50
|
+
* @param raw - Route with an optional leading `#` or `/` and an optional query.
|
|
51
|
+
*/
|
|
52
|
+
function parseRoute(raw) {
|
|
53
|
+
let trimmed = (raw ?? "").trim();
|
|
54
|
+
if (trimmed.startsWith("/#")) {
|
|
55
|
+
trimmed = trimmed.slice(2);
|
|
56
|
+
}
|
|
57
|
+
else if (trimmed.startsWith("#")) {
|
|
58
|
+
trimmed = trimmed.slice(1);
|
|
59
|
+
}
|
|
60
|
+
const queryIndex = trimmed.indexOf("?");
|
|
61
|
+
const before = queryIndex >= 0 ? trimmed.slice(0, queryIndex) : trimmed;
|
|
62
|
+
const queryString = queryIndex >= 0 ? trimmed.slice(queryIndex + 1) : "";
|
|
63
|
+
let pathname = before.trim();
|
|
64
|
+
pathname = pathname.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
65
|
+
return { pathname, query: new URLSearchParams(queryString) };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Rewrite legacy identity aliases to their canonical `identity/*` path.
|
|
69
|
+
*
|
|
70
|
+
* Unrelated routes are returned with only slash cleanup, so other apps keep
|
|
71
|
+
* their existing path casing. `devices?action=pair` becomes the pair route.
|
|
72
|
+
*
|
|
73
|
+
* @param raw - Requested route, possibly a legacy alias.
|
|
74
|
+
* @returns Canonical route, including any query that still applies.
|
|
75
|
+
*/
|
|
76
|
+
function canonicalizeRoute(raw) {
|
|
77
|
+
const { pathname, query } = parseRoute(raw);
|
|
78
|
+
if (!pathname) {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
const rewritten = rewritePathname(pathname, query);
|
|
82
|
+
const queryString = query.toString();
|
|
83
|
+
return queryString ? `${rewritten}?${queryString}` : rewritten;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Whether a route belongs to the Identity app after alias rewrite.
|
|
87
|
+
*
|
|
88
|
+
* @param raw - Route to test.
|
|
89
|
+
*/
|
|
90
|
+
function isIdentityPath(raw) {
|
|
91
|
+
const pathname = parseRoute(canonicalizeRoute(raw)).pathname;
|
|
92
|
+
return pathname === exports.IDENTITY_APP_PATH || pathname.startsWith(`${exports.IDENTITY_APP_PATH}/`);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Sidebar section for an Identity route.
|
|
96
|
+
*
|
|
97
|
+
* @param raw - Identity route or legacy alias.
|
|
98
|
+
* @returns The section, or `undefined` when the route is not Identity.
|
|
99
|
+
*/
|
|
100
|
+
function identitySectionFromPath(raw) {
|
|
101
|
+
return parseIdentityLocation(raw)?.section;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Detail, pairing, accept, and filter state for an Identity route.
|
|
105
|
+
*
|
|
106
|
+
* @param raw - Identity route or legacy alias.
|
|
107
|
+
* @returns Parsed location, or `undefined` when the route is not Identity.
|
|
108
|
+
*/
|
|
109
|
+
function parseIdentityLocation(raw) {
|
|
110
|
+
const canonical = canonicalizeRoute(raw);
|
|
111
|
+
const { pathname, query } = parseRoute(canonical);
|
|
112
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
113
|
+
if (parts[0] !== exports.IDENTITY_APP_PATH) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const section = IDENTITY_SECTIONS.has(parts[1])
|
|
117
|
+
? parts[1]
|
|
118
|
+
: "activity";
|
|
119
|
+
const rest = parts.slice(2);
|
|
120
|
+
const pairing = section === "devices" && rest[0] === "pair";
|
|
121
|
+
const accepting = section === "activity" && rest[0] === "accept";
|
|
122
|
+
const peopleAction = section === "people" && (rest[0] === "invite" || rest[0] === "share") ? rest[0] : undefined;
|
|
123
|
+
const creatingGroup = section === "groups" && rest[0] === "new";
|
|
124
|
+
const detailId = rest[0] &&
|
|
125
|
+
rest[0] !== "pair" &&
|
|
126
|
+
rest[0] !== "accept" &&
|
|
127
|
+
rest[0] !== "invite" &&
|
|
128
|
+
rest[0] !== "share" &&
|
|
129
|
+
rest[0] !== "new"
|
|
130
|
+
? rest[0]
|
|
131
|
+
: undefined;
|
|
132
|
+
const type = query.get("type");
|
|
133
|
+
return {
|
|
134
|
+
section,
|
|
135
|
+
pathname,
|
|
136
|
+
personId: section === "people" ? detailId : undefined,
|
|
137
|
+
peopleAction,
|
|
138
|
+
groupId: section === "groups" ? detailId : undefined,
|
|
139
|
+
creatingGroup,
|
|
140
|
+
deviceId: section === "devices" && !pairing ? detailId : undefined,
|
|
141
|
+
pairing,
|
|
142
|
+
accepting,
|
|
143
|
+
activityFilter: {
|
|
144
|
+
type: type && INVITE_TYPES.has(type)
|
|
145
|
+
? type
|
|
146
|
+
: undefined,
|
|
147
|
+
groupId: query.get("groupId") || undefined,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Activity route that shows one invite type, optionally one group.
|
|
153
|
+
*
|
|
154
|
+
* @param type - Invite type to show.
|
|
155
|
+
* @param groupId - Group to limit the list to.
|
|
156
|
+
*/
|
|
157
|
+
function identityActivityFilterPath(type, groupId) {
|
|
158
|
+
const query = new URLSearchParams();
|
|
159
|
+
query.set("type", type);
|
|
160
|
+
if (groupId) {
|
|
161
|
+
query.set("groupId", groupId);
|
|
162
|
+
}
|
|
163
|
+
return `${exports.IDENTITY_ACTIVITY_PATH}?${query.toString()}`;
|
|
164
|
+
}
|
|
165
|
+
function rewritePathname(pathname, query) {
|
|
166
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
167
|
+
const root = parts[0]?.toLowerCase() ?? "";
|
|
168
|
+
let rewritten;
|
|
169
|
+
if (root === "identity") {
|
|
170
|
+
rewritten = rewriteIdentity(parts.slice(1));
|
|
171
|
+
}
|
|
172
|
+
else if (root === "contacts") {
|
|
173
|
+
rewritten = ["identity", "people", ...singleId(parts.slice(1))];
|
|
174
|
+
}
|
|
175
|
+
else if (root === "groups") {
|
|
176
|
+
rewritten = ["identity", "groups", ...singleId(parts.slice(1))];
|
|
177
|
+
}
|
|
178
|
+
else if (root === "devices") {
|
|
179
|
+
rewritten = rewriteDevices(parts.slice(1));
|
|
180
|
+
}
|
|
181
|
+
else if (root === "invites") {
|
|
182
|
+
rewritten =
|
|
183
|
+
parts[1]?.toLowerCase() === "accept"
|
|
184
|
+
? ["identity", "activity", "accept"]
|
|
185
|
+
: ["identity", "activity"];
|
|
186
|
+
}
|
|
187
|
+
else if (root === "account") {
|
|
188
|
+
rewritten = ["identity", "account"];
|
|
189
|
+
}
|
|
190
|
+
if (!rewritten) {
|
|
191
|
+
return pathname;
|
|
192
|
+
}
|
|
193
|
+
if (rewritten.join("/") === exports.IDENTITY_DEVICES_PATH && query.get("action") === "pair") {
|
|
194
|
+
query.delete("action");
|
|
195
|
+
rewritten = ["identity", "devices", "pair"];
|
|
196
|
+
}
|
|
197
|
+
return rewritten.join("/");
|
|
198
|
+
}
|
|
199
|
+
function rewriteIdentity(rest) {
|
|
200
|
+
const section = rest[0]?.toLowerCase();
|
|
201
|
+
if (section === "people") {
|
|
202
|
+
return ["identity", "people", ...rewritePeople(rest.slice(1))];
|
|
203
|
+
}
|
|
204
|
+
if (section === "groups") {
|
|
205
|
+
return ["identity", "groups", ...rewriteGroups(rest.slice(1))];
|
|
206
|
+
}
|
|
207
|
+
if (section === "devices") {
|
|
208
|
+
return rewriteDevices(rest.slice(1));
|
|
209
|
+
}
|
|
210
|
+
if (section === "activity") {
|
|
211
|
+
return rest[1]?.toLowerCase() === "accept"
|
|
212
|
+
? ["identity", "activity", "accept"]
|
|
213
|
+
: ["identity", "activity"];
|
|
214
|
+
}
|
|
215
|
+
if (section === "account") {
|
|
216
|
+
return ["identity", "account"];
|
|
217
|
+
}
|
|
218
|
+
return ["identity", "activity"];
|
|
219
|
+
}
|
|
220
|
+
function rewritePeople(rest) {
|
|
221
|
+
const head = rest[0]?.toLowerCase();
|
|
222
|
+
if (head === "invite" || head === "share") {
|
|
223
|
+
return [head];
|
|
224
|
+
}
|
|
225
|
+
return singleId(rest);
|
|
226
|
+
}
|
|
227
|
+
function rewriteGroups(rest) {
|
|
228
|
+
if (rest[0]?.toLowerCase() === "new") {
|
|
229
|
+
return ["new"];
|
|
230
|
+
}
|
|
231
|
+
return singleId(rest);
|
|
232
|
+
}
|
|
233
|
+
function rewriteDevices(rest) {
|
|
234
|
+
const head = rest[0]?.toLowerCase();
|
|
235
|
+
if (!head) {
|
|
236
|
+
return ["identity", "devices"];
|
|
237
|
+
}
|
|
238
|
+
if (head === "pair") {
|
|
239
|
+
return ["identity", "devices", "pair"];
|
|
240
|
+
}
|
|
241
|
+
return ["identity", "devices", rest[0]];
|
|
242
|
+
}
|
|
243
|
+
function singleId(rest) {
|
|
244
|
+
return rest[0] ? [rest[0]] : [];
|
|
245
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const identity_routes_1 = require("./identity-routes");
|
|
4
|
+
const PERSON_ID = "abcdefghijklmnopqrstuvwxy";
|
|
5
|
+
const GROUP_ID = "zyxwvutsrqponmlkjihgfedcb";
|
|
6
|
+
const DEVICE_ID = "0123456789abcdefghijklmno";
|
|
7
|
+
describe("canonicalizeRoute", () => {
|
|
8
|
+
it.each([
|
|
9
|
+
["contacts", identity_routes_1.IDENTITY_PEOPLE_PATH],
|
|
10
|
+
[`contacts/${PERSON_ID}`, `${identity_routes_1.IDENTITY_PEOPLE_PATH}/${PERSON_ID}`],
|
|
11
|
+
["groups", identity_routes_1.IDENTITY_GROUPS_PATH],
|
|
12
|
+
[`groups/${GROUP_ID}`, `${identity_routes_1.IDENTITY_GROUPS_PATH}/${GROUP_ID}`],
|
|
13
|
+
["devices", identity_routes_1.IDENTITY_DEVICES_PATH],
|
|
14
|
+
[`devices/${DEVICE_ID}`, `${identity_routes_1.IDENTITY_DEVICES_PATH}/${DEVICE_ID}`],
|
|
15
|
+
["devices?action=pair", identity_routes_1.IDENTITY_PAIR_PATH],
|
|
16
|
+
["devices?action=pair&foo=bar", `${identity_routes_1.IDENTITY_PAIR_PATH}?foo=bar`],
|
|
17
|
+
["invites", identity_routes_1.IDENTITY_ACTIVITY_PATH],
|
|
18
|
+
["invites/accept", identity_routes_1.IDENTITY_ACCEPT_PATH],
|
|
19
|
+
["account", identity_routes_1.IDENTITY_ACCOUNT_PATH],
|
|
20
|
+
["account?x=1", `${identity_routes_1.IDENTITY_ACCOUNT_PATH}?x=1`],
|
|
21
|
+
["identity/account", identity_routes_1.IDENTITY_ACCOUNT_PATH],
|
|
22
|
+
["identity/people/invite", identity_routes_1.IDENTITY_PEOPLE_INVITE_PATH],
|
|
23
|
+
["identity/people/SHARE", identity_routes_1.IDENTITY_PEOPLE_SHARE_PATH],
|
|
24
|
+
["identity/groups/new", identity_routes_1.IDENTITY_GROUP_NEW_PATH],
|
|
25
|
+
["identity", identity_routes_1.IDENTITY_ACTIVITY_PATH],
|
|
26
|
+
[`/#contacts/${PERSON_ID}?x=1`, `${identity_routes_1.IDENTITY_PEOPLE_PATH}/${PERSON_ID}?x=1`],
|
|
27
|
+
["tools/abc", "tools/abc"],
|
|
28
|
+
["", ""],
|
|
29
|
+
])("rewrites %s to %s", (input, expected) => {
|
|
30
|
+
expect((0, identity_routes_1.canonicalizeRoute)(input)).toBe(expected);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("parseIdentityLocation", () => {
|
|
34
|
+
it("parses detail routes and activity filters", () => {
|
|
35
|
+
expect((0, identity_routes_1.parseIdentityLocation)(`contacts/${PERSON_ID}`)?.personId).toBe(PERSON_ID);
|
|
36
|
+
expect((0, identity_routes_1.parseIdentityLocation)(`groups/${GROUP_ID}`)?.groupId).toBe(GROUP_ID);
|
|
37
|
+
expect((0, identity_routes_1.parseIdentityLocation)("devices?action=pair")?.pairing).toBe(true);
|
|
38
|
+
expect((0, identity_routes_1.parseIdentityLocation)(`devices/${DEVICE_ID}`)?.deviceId).toBe(DEVICE_ID);
|
|
39
|
+
expect((0, identity_routes_1.parseIdentityLocation)("invites/accept")?.accepting).toBe(true);
|
|
40
|
+
expect((0, identity_routes_1.parseIdentityLocation)(identity_routes_1.IDENTITY_PEOPLE_INVITE_PATH)?.peopleAction).toBe("invite");
|
|
41
|
+
expect((0, identity_routes_1.parseIdentityLocation)(identity_routes_1.IDENTITY_PEOPLE_SHARE_PATH)?.peopleAction).toBe("share");
|
|
42
|
+
expect((0, identity_routes_1.parseIdentityLocation)(identity_routes_1.IDENTITY_GROUP_NEW_PATH)?.creatingGroup).toBe(true);
|
|
43
|
+
expect((0, identity_routes_1.parseIdentityLocation)((0, identity_routes_1.identityActivityFilterPath)("group", GROUP_ID))?.activityFilter).toEqual({
|
|
44
|
+
type: "group",
|
|
45
|
+
groupId: GROUP_ID,
|
|
46
|
+
});
|
|
47
|
+
expect((0, identity_routes_1.identitySectionFromPath)("contacts")).toBe("people");
|
|
48
|
+
expect((0, identity_routes_1.identitySectionFromPath)("account")).toBe("account");
|
|
49
|
+
expect((0, identity_routes_1.isIdentityPath)("settings")).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity app shell.
|
|
3
|
+
*
|
|
4
|
+
* Activity, People, Groups, Devices, and Account stay one top-level tab. The
|
|
5
|
+
* route selects the section, and detail routes keep that section highlighted.
|
|
6
|
+
*/
|
|
7
|
+
export declare function IdentityScreen(props: {
|
|
8
|
+
path?: string;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IdentityScreen = IdentityScreen;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const responsive_section_nav_1 = require("../../components/responsive-section-nav");
|
|
7
|
+
const globals_1 = require("../../globals");
|
|
8
|
+
const hooks_1 = require("../../hooks");
|
|
9
|
+
const ui_loader_1 = require("../../ui-router/ui-loader");
|
|
10
|
+
const account_screen_1 = require("../account/account-screen");
|
|
11
|
+
const contact_details_1 = require("../contacts/contact-details");
|
|
12
|
+
const contact_list_1 = require("../contacts/contact-list");
|
|
13
|
+
const devices_list_1 = require("../devices/devices-list");
|
|
14
|
+
const group_details_1 = require("../groups/group-details");
|
|
15
|
+
const group_list_1 = require("../groups/group-list");
|
|
16
|
+
const accept_invite_1 = require("../invites/accept-invite");
|
|
17
|
+
const activity_view_1 = require("../invites/activity-view");
|
|
18
|
+
const pending_invites_list_1 = require("../invites/pending-invites-list");
|
|
19
|
+
const identity_routes_1 = require("./identity-routes");
|
|
20
|
+
const SECTION_PATHS = {
|
|
21
|
+
activity: identity_routes_1.IDENTITY_ACTIVITY_PATH,
|
|
22
|
+
people: identity_routes_1.IDENTITY_PEOPLE_PATH,
|
|
23
|
+
groups: identity_routes_1.IDENTITY_GROUPS_PATH,
|
|
24
|
+
devices: identity_routes_1.IDENTITY_DEVICES_PATH,
|
|
25
|
+
account: identity_routes_1.IDENTITY_ACCOUNT_PATH,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Identity app shell.
|
|
29
|
+
*
|
|
30
|
+
* Activity, People, Groups, Devices, and Account stay one top-level tab. The
|
|
31
|
+
* route selects the section, and detail routes keep that section highlighted.
|
|
32
|
+
*/
|
|
33
|
+
function IdentityScreen(props) {
|
|
34
|
+
const [livePath] = (0, hooks_1.useObservable)(globals_1.mainContentPath);
|
|
35
|
+
const location = (0, identity_routes_1.parseIdentityLocation)(props.path ?? livePath) ?? (0, identity_routes_1.parseIdentityLocation)(identity_routes_1.IDENTITY_ACTIVITY_PATH);
|
|
36
|
+
const pending = (0, pending_invites_list_1.useInviteRows)({ statuses: ["pending"] });
|
|
37
|
+
const rows = pending?.rows;
|
|
38
|
+
if (!location) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return ((0, jsx_runtime_1.jsx)(responsive_section_nav_1.ResponsiveSectionNav, { label: "Identity", activeSectionId: location.section, onSelect: (sectionId) => (0, globals_1.mainContentPath)(SECTION_PATHS[sectionId]), toolbar: (0, jsx_runtime_1.jsx)(IdentityQuickActions, {}), sections: [
|
|
42
|
+
{
|
|
43
|
+
id: "activity",
|
|
44
|
+
label: "Activity",
|
|
45
|
+
iconClassName: "bi-inbox",
|
|
46
|
+
badge: badgeCount(rows, "attention"),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "people",
|
|
50
|
+
label: "People",
|
|
51
|
+
iconClassName: "bi-person-fill-check",
|
|
52
|
+
badge: badgeCount(rows, "contact"),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "groups",
|
|
56
|
+
label: "Groups",
|
|
57
|
+
iconClassName: "bi-people-fill",
|
|
58
|
+
badge: badgeCount(rows, "group"),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "devices",
|
|
62
|
+
label: "Devices",
|
|
63
|
+
iconClassName: "bi-laptop",
|
|
64
|
+
badge: badgeCount(rows, "device"),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "account",
|
|
68
|
+
label: "Account",
|
|
69
|
+
iconClassName: "bi-person-circle",
|
|
70
|
+
},
|
|
71
|
+
], children: (0, jsx_runtime_1.jsx)(IdentityBody, { location: location }) }));
|
|
72
|
+
}
|
|
73
|
+
function IdentityBody(props) {
|
|
74
|
+
const { location } = props;
|
|
75
|
+
if (location.accepting) {
|
|
76
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "container-fluid py-3", style: { maxWidth: 720 }, children: [(0, jsx_runtime_1.jsx)("a", { href: `#${identity_routes_1.IDENTITY_ACTIVITY_PATH}`, className: "btn btn-link btn-sm px-0 mb-2", children: "Back to Activity" }), (0, jsx_runtime_1.jsxs)("h4", { className: "mb-3", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-envelope-paper me-2" }), "Accept an invite"] }), (0, jsx_runtime_1.jsx)("div", { className: "card", children: (0, jsx_runtime_1.jsx)("div", { className: "card-body", children: (0, jsx_runtime_1.jsx)(accept_invite_1.AcceptInvite, { onDone: () => (0, globals_1.mainContentPath)(identity_routes_1.IDENTITY_ACTIVITY_PATH) }) }) })] }));
|
|
77
|
+
}
|
|
78
|
+
if (location.section === "people") {
|
|
79
|
+
return location.personId ? ((0, jsx_runtime_1.jsx)(DetailFrame, { backHref: `#${identity_routes_1.IDENTITY_PEOPLE_PATH}`, backLabel: "Back to people", children: (0, jsx_runtime_1.jsx)(contact_details_1.ContactDetails, { userId: location.personId }) })) : ((0, jsx_runtime_1.jsx)(contact_list_1.ContactList, { action: location.peopleAction }));
|
|
80
|
+
}
|
|
81
|
+
if (location.section === "groups") {
|
|
82
|
+
return location.groupId ? ((0, jsx_runtime_1.jsx)(DetailFrame, { backHref: `#${identity_routes_1.IDENTITY_GROUPS_PATH}`, backLabel: "Back to groups", children: (0, jsx_runtime_1.jsx)(group_details_1.GroupDetails, { groupId: location.groupId }) })) : ((0, jsx_runtime_1.jsx)(group_list_1.GroupList, { creating: location.creatingGroup }));
|
|
83
|
+
}
|
|
84
|
+
if (location.section === "devices") {
|
|
85
|
+
const mode = location.pairing ? "pair" : location.deviceId ? "detail" : "list";
|
|
86
|
+
return (0, jsx_runtime_1.jsx)(devices_list_1.DevicesSection, { mode: mode, deviceId: location.deviceId });
|
|
87
|
+
}
|
|
88
|
+
if (location.section === "account") {
|
|
89
|
+
return (0, jsx_runtime_1.jsx)(account_screen_1.AccountScreen, {});
|
|
90
|
+
}
|
|
91
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "container-xxl p-3", children: (0, jsx_runtime_1.jsx)(activity_view_1.ActivityView, { filter: location.activityFilter }) }));
|
|
92
|
+
}
|
|
93
|
+
function IdentityQuickActions() {
|
|
94
|
+
const [desktop] = (0, hooks_1.useObservable)(globals_1.isDesktop);
|
|
95
|
+
const [showAdd, setShowAdd] = (0, react_1.useState)(false);
|
|
96
|
+
const addActions = [
|
|
97
|
+
{
|
|
98
|
+
path: identity_routes_1.IDENTITY_PEOPLE_INVITE_PATH,
|
|
99
|
+
label: "Add person",
|
|
100
|
+
icon: "bi-person-plus",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
path: identity_routes_1.IDENTITY_GROUP_NEW_PATH,
|
|
104
|
+
label: "Create group",
|
|
105
|
+
icon: "bi-people",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
path: identity_routes_1.IDENTITY_PAIR_PATH,
|
|
109
|
+
label: "Add device",
|
|
110
|
+
icon: "bi-laptop",
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
if (desktop) {
|
|
114
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "d-grid align-items-center gap-2 border-bottom p-2", style: { gridTemplateColumns: "minmax(0, 1fr) auto minmax(0, 1fr)" }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "overflow-hidden", children: [(0, jsx_runtime_1.jsx)("div", { className: "fw-semibold", children: "Identity" }), (0, jsx_runtime_1.jsx)("small", { className: "text-muted text-truncate d-block", children: "People, groups, and your devices" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "d-flex align-items-center justify-content-center gap-2", children: [addActions.map((action) => ((0, jsx_runtime_1.jsxs)("a", { href: `#${action.path}`, className: "btn btn-outline-primary btn-sm", children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${action.icon} me-1` }), action.label] }, action.path))), (0, jsx_runtime_1.jsxs)("a", { href: `#${identity_routes_1.IDENTITY_ACCEPT_PATH}`, className: "btn btn-primary btn-sm", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-qr-code-scan me-1" }), "Use invite"] })] }), (0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true" })] }));
|
|
115
|
+
}
|
|
116
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "position-relative d-flex align-items-center gap-2 border-bottom p-2", children: [(0, jsx_runtime_1.jsx)("div", { className: "me-auto fw-semibold", children: "Identity" }), (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "btn btn-outline-primary btn-sm", "aria-expanded": showAdd, onClick: () => setShowAdd((open) => !open), children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-plus-lg me-1" }), "Add"] }), (0, jsx_runtime_1.jsxs)("a", { href: `#${identity_routes_1.IDENTITY_ACCEPT_PATH}`, className: "btn btn-primary btn-sm", children: [(0, jsx_runtime_1.jsx)("i", { className: "bi bi-qr-code-scan me-1" }), "Invite"] }), showAdd ? ((0, jsx_runtime_1.jsx)("div", { className: "card position-absolute end-0 top-100 mt-1 shadow-sm", style: { zIndex: 20, minWidth: 190 }, children: (0, jsx_runtime_1.jsx)("div", { className: "list-group list-group-flush", children: addActions.map((action) => ((0, jsx_runtime_1.jsxs)("a", { href: `#${action.path}`, className: "list-group-item list-group-item-action d-flex align-items-center gap-2", onClick: () => setShowAdd(false), children: [(0, jsx_runtime_1.jsx)("i", { className: `bi ${action.icon}` }), action.label] }, action.path))) }) })) : null] }));
|
|
117
|
+
}
|
|
118
|
+
function DetailFrame(props) {
|
|
119
|
+
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("a", { href: props.backHref, className: "btn btn-link btn-sm px-3 pt-3", children: props.backLabel }), props.children] }));
|
|
120
|
+
}
|
|
121
|
+
function badgeCount(rows, kind) {
|
|
122
|
+
if (!rows) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const count = rows.filter((row) => kind === "attention"
|
|
126
|
+
? (0, pending_invites_list_1.classifyInvite)(row) === "attention"
|
|
127
|
+
: row.type === kind && (0, pending_invites_list_1.classifyInvite)(row) === "attention").length;
|
|
128
|
+
return count > 0 ? count : undefined;
|
|
129
|
+
}
|
|
130
|
+
(0, ui_loader_1.registerInternalPeersUI)({
|
|
131
|
+
peersUIId: "00identity0screen00000001",
|
|
132
|
+
component: IdentityScreen,
|
|
133
|
+
routes: [
|
|
134
|
+
{
|
|
135
|
+
isMatch: (_props, context) => {
|
|
136
|
+
const path = context.path.split("?")[0];
|
|
137
|
+
return path === "identity" || path.startsWith("identity/");
|
|
138
|
+
},
|
|
139
|
+
uiCategory: "screen",
|
|
140
|
+
priority: 3,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
7
|
+
const react_1 = require("@testing-library/react");
|
|
8
|
+
const user_event_1 = __importDefault(require("@testing-library/user-event"));
|
|
9
|
+
const globals_1 = require("../../globals");
|
|
10
|
+
const identity_screen_1 = require("./identity-screen");
|
|
11
|
+
jest.mock("../../globals", () => {
|
|
12
|
+
const { observable } = jest.requireActual("@peers-app/peers-sdk");
|
|
13
|
+
return {
|
|
14
|
+
isDesktop: observable(true),
|
|
15
|
+
mainContentPath: observable("identity/activity"),
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
jest.mock("../../ui-router/ui-loader", () => ({
|
|
19
|
+
registerInternalPeersUI: jest.fn(),
|
|
20
|
+
}));
|
|
21
|
+
jest.mock("../contacts/contact-list", () => ({
|
|
22
|
+
ContactList: () => (0, jsx_runtime_1.jsx)("div", { children: "People view" }),
|
|
23
|
+
}));
|
|
24
|
+
jest.mock("../contacts/contact-details", () => ({
|
|
25
|
+
ContactDetails: ({ userId }) => (0, jsx_runtime_1.jsxs)("div", { children: ["Person ", userId] }),
|
|
26
|
+
}));
|
|
27
|
+
jest.mock("../groups/group-list", () => ({
|
|
28
|
+
GroupList: () => (0, jsx_runtime_1.jsx)("div", { children: "Groups view" }),
|
|
29
|
+
}));
|
|
30
|
+
jest.mock("../groups/group-details", () => ({
|
|
31
|
+
GroupDetails: ({ groupId }) => (0, jsx_runtime_1.jsxs)("div", { children: ["Group ", groupId] }),
|
|
32
|
+
}));
|
|
33
|
+
jest.mock("../devices/devices-list", () => ({
|
|
34
|
+
DevicesSection: ({ mode }) => (0, jsx_runtime_1.jsxs)("div", { children: ["Devices ", mode] }),
|
|
35
|
+
}));
|
|
36
|
+
jest.mock("../invites/accept-invite", () => ({
|
|
37
|
+
AcceptInvite: () => (0, jsx_runtime_1.jsx)("div", { children: "Accept invite view" }),
|
|
38
|
+
}));
|
|
39
|
+
jest.mock("../invites/activity-view", () => ({
|
|
40
|
+
ActivityView: () => (0, jsx_runtime_1.jsx)("div", { children: "Activity view" }),
|
|
41
|
+
}));
|
|
42
|
+
jest.mock("../account/account-screen", () => ({
|
|
43
|
+
AccountScreen: () => (0, jsx_runtime_1.jsx)("div", { children: "Account view" }),
|
|
44
|
+
}));
|
|
45
|
+
jest.mock("../invites/pending-invites-list", () => ({
|
|
46
|
+
useInviteRows: () => ({
|
|
47
|
+
rows: [
|
|
48
|
+
{
|
|
49
|
+
inviteId: "inboundcontact0000000001",
|
|
50
|
+
type: "contact",
|
|
51
|
+
status: "pending",
|
|
52
|
+
direction: "in",
|
|
53
|
+
mode: "contact",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
hasHistory: false,
|
|
57
|
+
}),
|
|
58
|
+
classifyInvite: (row) => row.status === "pending" && row.direction === "in" && row.type === "contact"
|
|
59
|
+
? "attention"
|
|
60
|
+
: "waiting",
|
|
61
|
+
}));
|
|
62
|
+
const PERSON_ID = "abcdefghijklmnopqrstuvwxy";
|
|
63
|
+
describe("IdentityScreen", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
(0, globals_1.isDesktop)(true);
|
|
66
|
+
(0, globals_1.mainContentPath)("identity/activity");
|
|
67
|
+
});
|
|
68
|
+
it("highlights the parent section for a person route and keeps Activity counted", () => {
|
|
69
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(identity_screen_1.IdentityScreen, { path: `identity/people/${PERSON_ID}` }));
|
|
70
|
+
expect(react_1.screen.getByRole("button", { name: /People/ })).toHaveAttribute("aria-current", "page");
|
|
71
|
+
expect(react_1.screen.getByText(`Person ${PERSON_ID}`)).toBeInTheDocument();
|
|
72
|
+
expect(react_1.screen.getByRole("button", { name: /Activity/ })).toHaveTextContent("1");
|
|
73
|
+
});
|
|
74
|
+
it("renders the accept flow on the activity section", () => {
|
|
75
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(identity_screen_1.IdentityScreen, { path: "invites/accept" }));
|
|
76
|
+
expect(react_1.screen.getByText("Accept invite view")).toBeInTheDocument();
|
|
77
|
+
expect(react_1.screen.getByRole("button", { name: /Activity/ })).toHaveAttribute("aria-current", "page");
|
|
78
|
+
});
|
|
79
|
+
it("renders registration on the account section", () => {
|
|
80
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(identity_screen_1.IdentityScreen, { path: "account" }));
|
|
81
|
+
expect(react_1.screen.getByText("Account view")).toBeInTheDocument();
|
|
82
|
+
expect(react_1.screen.getByRole("button", { name: /Account/ })).toHaveAttribute("aria-current", "page");
|
|
83
|
+
});
|
|
84
|
+
it("selects a section through the canonical route", async () => {
|
|
85
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(identity_screen_1.IdentityScreen, { path: "identity/activity" }));
|
|
86
|
+
await user_event_1.default.click(react_1.screen.getByRole("button", { name: /Devices/ }));
|
|
87
|
+
expect((0, globals_1.mainContentPath)()).toBe("identity/devices");
|
|
88
|
+
});
|
|
89
|
+
it("keeps all desktop quick actions route-backed", () => {
|
|
90
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(identity_screen_1.IdentityScreen, { path: "identity/activity" }));
|
|
91
|
+
expect(react_1.screen.getByRole("link", { name: /Add person/ })).toHaveAttribute("href", "#identity/people/invite");
|
|
92
|
+
expect(react_1.screen.getByRole("link", { name: /Create group/ })).toHaveAttribute("href", "#identity/groups/new");
|
|
93
|
+
expect(react_1.screen.getByRole("link", { name: /Add device/ })).toHaveAttribute("href", "#identity/devices/pair");
|
|
94
|
+
expect(react_1.screen.getByRole("link", { name: /Use invite/ })).toHaveAttribute("href", "#identity/activity/accept");
|
|
95
|
+
});
|
|
96
|
+
it("collapses add actions on a narrow screen", async () => {
|
|
97
|
+
(0, globals_1.isDesktop)(false);
|
|
98
|
+
(0, react_1.render)((0, jsx_runtime_1.jsx)(identity_screen_1.IdentityScreen, { path: "identity/activity" }));
|
|
99
|
+
await user_event_1.default.click(react_1.screen.getByRole("button", { name: "Add" }));
|
|
100
|
+
expect(react_1.screen.getByRole("link", { name: /Add person/ })).toBeInTheDocument();
|
|
101
|
+
expect(react_1.screen.getByRole("link", { name: /Create group/ })).toBeInTheDocument();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.identityApp = void 0;
|
|
4
|
+
var identity_app_1 = require("../../system-apps/identity.app");
|
|
5
|
+
Object.defineProperty(exports, "identityApp", { enumerable: true, get: function () { return identity_app_1.identityApp; } });
|
|
6
|
+
require("./identity-screen");
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InviteContactPanel = InviteContactPanel;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const loading_indicator_1 = require("../../components/loading-indicator");
|
|
7
|
+
const invite_share_1 = require("../invites/invite-share");
|
|
8
|
+
const invites_ipc_1 = require("../invites/invites-ipc");
|
|
9
|
+
/** Issues a one-off contact invite and shows it as a QR code and link. */
|
|
10
|
+
function InviteContactPanel({ onClose }) {
|
|
11
|
+
const [invite, setInvite] = (0, react_1.useState)(undefined);
|
|
12
|
+
const [error, setError] = (0, react_1.useState)("");
|
|
13
|
+
(0, react_1.useEffect)(() => {
|
|
14
|
+
let cancelled = false;
|
|
15
|
+
void (0, invites_ipc_1.inviteTools)()
|
|
16
|
+
.then((tools) => tools.createContactInvite({}))
|
|
17
|
+
.then(({ invite: created }) => {
|
|
18
|
+
if (!cancelled)
|
|
19
|
+
setInvite(created);
|
|
20
|
+
})
|
|
21
|
+
.catch((err) => {
|
|
22
|
+
if (!cancelled)
|
|
23
|
+
setError(err.message || "Could not create an invite.");
|
|
24
|
+
});
|
|
25
|
+
return () => {
|
|
26
|
+
cancelled = true;
|
|
27
|
+
};
|
|
28
|
+
}, []);
|
|
29
|
+
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: "Invite someone" }), error ? (0, jsx_runtime_1.jsx)("div", { className: "alert alert-danger py-2", children: error }) : null, invite ? ((0, jsx_runtime_1.jsx)(invite_share_1.InviteShare, { invite: invite, description: "Send this link or show the QR. When they accept, you are connected \u2014 no need to be online at the same time.", shareText: "Connect with me on Peers:", onDone: onClose })) : !error ? ((0, jsx_runtime_1.jsx)(loading_indicator_1.LoadingIndicator, {})) : null] }) }));
|
|
30
|
+
}
|