@peers-app/peers-ui 0.25.2 → 0.25.4
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 +44 -2
- package/dist/screens/invites/accept-invite.test.d.ts +1 -0
- package/dist/screens/invites/accept-invite.test.js +103 -0
- 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 +115 -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 +75 -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.test.tsx +115 -0
- package/src/screens/invites/accept-invite.tsx +50 -3
- 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 +129 -0
- package/src/screens/invites/invites-ipc.ts +6 -55
- package/src/screens/invites/pending-invites-list.tsx +94 -62
- 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
|
@@ -1,96 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { copyToClipboard, getMe, thisDeviceId } from "@peers-app/peers-sdk";
|
|
2
|
+
import { useState } from "react";
|
|
3
3
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
4
4
|
import { Tooltip } from "../../components/tooltip";
|
|
5
|
-
import { useObservable } from "../../hooks";
|
|
5
|
+
import { useObservable, usePromise } from "../../hooks";
|
|
6
6
|
import {
|
|
7
7
|
persistPeersServicesRegistration,
|
|
8
8
|
registrationStatusPvar,
|
|
9
9
|
} from "../../peers-services-registration";
|
|
10
|
-
import {
|
|
11
|
-
import { inviteTools } from "../invites/invites-ipc";
|
|
12
|
-
|
|
13
|
-
const PROFILE_QR_INFO = `### Your profile QR
|
|
14
|
-
|
|
15
|
-
Anyone who scans this (or opens the link) can send you a contact request. Requests show up under **Contacts → Pending** and nothing happens until you confirm.
|
|
16
|
-
|
|
17
|
-
### Regenerate
|
|
18
|
-
|
|
19
|
-
Regenerating invalidates this QR **and every other invite link you have issued**. Use it if the QR leaked somewhere you did not intend.`;
|
|
20
|
-
|
|
21
|
-
/** Reusable profile invite with a regenerate control that bumps the invite epoch. */
|
|
22
|
-
function ProfileInviteSection() {
|
|
23
|
-
const [invite, setInvite] = useState<ICreatedInvite | undefined>(undefined);
|
|
24
|
-
const [busy, setBusy] = useState(false);
|
|
25
|
-
const [error, setError] = useState<string | null>(null);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
let cancelled = false;
|
|
29
|
-
void inviteTools()
|
|
30
|
-
.then((tools) => tools.createContactInvite({ profile: true }))
|
|
31
|
-
.then(({ invite: created }) => {
|
|
32
|
-
if (!cancelled) setInvite(created);
|
|
33
|
-
})
|
|
34
|
-
.catch((err) => {
|
|
35
|
-
if (!cancelled) setError((err as Error).message || "Could not load your profile QR.");
|
|
36
|
-
});
|
|
37
|
-
return () => {
|
|
38
|
-
cancelled = true;
|
|
39
|
-
};
|
|
40
|
-
}, []);
|
|
41
|
-
|
|
42
|
-
const regenerate = async () => {
|
|
43
|
-
if (
|
|
44
|
-
!confirm(
|
|
45
|
-
"Regenerate your profile QR? Every invite link you have issued so far will stop working.",
|
|
46
|
-
)
|
|
47
|
-
) {
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
setBusy(true);
|
|
51
|
-
setError(null);
|
|
52
|
-
try {
|
|
53
|
-
const tools = await inviteTools();
|
|
54
|
-
const { invite: created } = await tools.regenerateProfileInvite();
|
|
55
|
-
setInvite(created);
|
|
56
|
-
} catch (err) {
|
|
57
|
-
setError((err as Error).message || "Could not regenerate.");
|
|
58
|
-
} finally {
|
|
59
|
-
setBusy(false);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<div className="card mb-3">
|
|
65
|
-
<div className="card-body">
|
|
66
|
-
<h5 className="card-title d-flex align-items-center gap-2">
|
|
67
|
-
Profile QR
|
|
68
|
-
<Tooltip markdownContent={PROFILE_QR_INFO} />
|
|
69
|
-
</h5>
|
|
70
|
-
{error ? <div className="alert alert-danger py-2">{error}</div> : null}
|
|
71
|
-
{invite ? (
|
|
72
|
-
<InviteShare
|
|
73
|
-
invite={invite}
|
|
74
|
-
description="Let people connect with you by scanning this or opening the link. You confirm each request."
|
|
75
|
-
shareText="Connect with me on Peers:"
|
|
76
|
-
/>
|
|
77
|
-
) : !error ? (
|
|
78
|
-
<LoadingIndicator />
|
|
79
|
-
) : null}
|
|
80
|
-
<div className="text-center mt-3">
|
|
81
|
-
<button
|
|
82
|
-
type="button"
|
|
83
|
-
className="btn btn-outline-danger btn-sm"
|
|
84
|
-
onClick={() => void regenerate()}
|
|
85
|
-
disabled={busy || !invite}
|
|
86
|
-
>
|
|
87
|
-
{busy ? "Regenerating…" : "Regenerate (revokes all invites)"}
|
|
88
|
-
</button>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
10
|
+
import { identityTools } from "../device-pairing/identity-ipc";
|
|
94
11
|
|
|
95
12
|
const INFO_TEXT = `### What does registering do?
|
|
96
13
|
|
|
@@ -109,8 +26,11 @@ Your **secret key never leaves your device**. Only your public key is shared wit
|
|
|
109
26
|
|
|
110
27
|
No. Peers works fully in local/peer-to-peer mode without registration. Cloud features simply won't be available until you register.`;
|
|
111
28
|
|
|
29
|
+
/** Identity profile, public identifiers, device shortcuts, and service registration. */
|
|
112
30
|
export function AccountScreen() {
|
|
113
31
|
const [registrationStatus] = useObservable(registrationStatusPvar);
|
|
32
|
+
const [deviceId] = useObservable(thisDeviceId);
|
|
33
|
+
const me = usePromise(getMe);
|
|
114
34
|
const [isRegistering, setIsRegistering] = useState(false);
|
|
115
35
|
const [error, setError] = useState<string | null>(null);
|
|
116
36
|
const [success, setSuccess] = useState<string | null>(null);
|
|
@@ -123,8 +43,8 @@ export function AccountScreen() {
|
|
|
123
43
|
setSuccess(null);
|
|
124
44
|
|
|
125
45
|
try {
|
|
126
|
-
const token = await
|
|
127
|
-
persistPeersServicesRegistration(token);
|
|
46
|
+
const { token } = await (await identityTools()).registerWithPeersServices();
|
|
47
|
+
await persistPeersServicesRegistration(token);
|
|
128
48
|
setSuccess("Account registered successfully.");
|
|
129
49
|
} catch (err) {
|
|
130
50
|
setError(`Registration failed: ${String(err)}`);
|
|
@@ -133,12 +53,19 @@ export function AccountScreen() {
|
|
|
133
53
|
}
|
|
134
54
|
};
|
|
135
55
|
|
|
56
|
+
if (!me) {
|
|
57
|
+
return <LoadingIndicator />;
|
|
58
|
+
}
|
|
59
|
+
|
|
136
60
|
return (
|
|
137
|
-
<div className="container-
|
|
138
|
-
<
|
|
139
|
-
<
|
|
140
|
-
|
|
141
|
-
|
|
61
|
+
<div className="container-xxl p-3">
|
|
62
|
+
<div className="mb-3">
|
|
63
|
+
<h4 className="mb-1 d-flex align-items-center gap-2">
|
|
64
|
+
<i className="bi bi-person-circle" />
|
|
65
|
+
Account
|
|
66
|
+
</h4>
|
|
67
|
+
<div className="small text-muted">Your public identity and account-level services</div>
|
|
68
|
+
</div>
|
|
142
69
|
|
|
143
70
|
{error && (
|
|
144
71
|
<div className="alert alert-danger alert-dismissible" role="alert">
|
|
@@ -166,7 +93,61 @@ export function AccountScreen() {
|
|
|
166
93
|
</div>
|
|
167
94
|
)}
|
|
168
95
|
|
|
169
|
-
<
|
|
96
|
+
<div className="card mb-3">
|
|
97
|
+
<div className="card-body d-flex align-items-center gap-3 flex-wrap">
|
|
98
|
+
<span
|
|
99
|
+
className="rounded-circle bg-primary-subtle text-primary-emphasis d-inline-flex align-items-center justify-content-center fs-4 fw-semibold"
|
|
100
|
+
style={{ width: 58, height: 58 }}
|
|
101
|
+
aria-hidden="true"
|
|
102
|
+
>
|
|
103
|
+
{profileInitials(me.name)}
|
|
104
|
+
</span>
|
|
105
|
+
<div className="flex-grow-1">
|
|
106
|
+
<h5 className="card-title mb-1">{me.name || "Unnamed user"}</h5>
|
|
107
|
+
<div className="small text-muted">Peers identity</div>
|
|
108
|
+
</div>
|
|
109
|
+
<div className="d-flex gap-2 flex-wrap">
|
|
110
|
+
<a href="#settings" className="btn btn-outline-secondary btn-sm">
|
|
111
|
+
Edit display name
|
|
112
|
+
</a>
|
|
113
|
+
<a href="#identity/devices" className="btn btn-outline-primary btn-sm">
|
|
114
|
+
Manage devices
|
|
115
|
+
</a>
|
|
116
|
+
<a href="#identity/devices/pair" className="btn btn-primary btn-sm">
|
|
117
|
+
Add device
|
|
118
|
+
</a>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
<div className="card mb-3">
|
|
124
|
+
<div className="card-body">
|
|
125
|
+
<h5 className="card-title">Technical identity</h5>
|
|
126
|
+
<p className="small text-muted">
|
|
127
|
+
Share these values only when diagnosing identity or connection problems.
|
|
128
|
+
</p>
|
|
129
|
+
<TechnicalValue
|
|
130
|
+
label="User ID"
|
|
131
|
+
value={me.userId}
|
|
132
|
+
help="Your stable identifier across Peers."
|
|
133
|
+
/>
|
|
134
|
+
<TechnicalValue
|
|
135
|
+
label="Signing key"
|
|
136
|
+
value={me.publicKey}
|
|
137
|
+
help="Other people use this public key to verify your signatures."
|
|
138
|
+
/>
|
|
139
|
+
<TechnicalValue
|
|
140
|
+
label="Encryption key"
|
|
141
|
+
value={me.publicBoxKey}
|
|
142
|
+
help="Other people use this public key to encrypt data for you."
|
|
143
|
+
/>
|
|
144
|
+
<TechnicalValue
|
|
145
|
+
label="Current device ID"
|
|
146
|
+
value={deviceId || "Not available"}
|
|
147
|
+
help="Identifies this installation on Peers networks."
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
170
151
|
|
|
171
152
|
<div className="card">
|
|
172
153
|
<div className="card-body">
|
|
@@ -238,3 +219,43 @@ export function AccountScreen() {
|
|
|
238
219
|
</div>
|
|
239
220
|
);
|
|
240
221
|
}
|
|
222
|
+
|
|
223
|
+
function TechnicalValue(props: { label: string; value: string; help: string }) {
|
|
224
|
+
const [copied, setCopied] = useState(false);
|
|
225
|
+
const copy = async () => {
|
|
226
|
+
if (await copyToClipboard(props.value)) {
|
|
227
|
+
setCopied(true);
|
|
228
|
+
window.setTimeout(() => setCopied(false), 1500);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
return (
|
|
232
|
+
<div className="border-top py-3">
|
|
233
|
+
<div className="d-flex align-items-center gap-2 mb-1">
|
|
234
|
+
<strong className="small">{props.label}</strong>
|
|
235
|
+
<Tooltip markdownContent={props.help} />
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
className="btn btn-link btn-sm ms-auto py-0"
|
|
239
|
+
onClick={() => void copy()}
|
|
240
|
+
aria-label={`Copy ${props.label}`}
|
|
241
|
+
>
|
|
242
|
+
<i className={`bi ${copied ? "bi-check-lg" : "bi-copy"} me-1`} />
|
|
243
|
+
{copied ? "Copied" : "Copy"}
|
|
244
|
+
</button>
|
|
245
|
+
</div>
|
|
246
|
+
<code className="small text-break user-select-all">{props.value}</code>
|
|
247
|
+
</div>
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function profileInitials(name: string): string {
|
|
252
|
+
return (
|
|
253
|
+
name
|
|
254
|
+
.trim()
|
|
255
|
+
.split(/\s+/)
|
|
256
|
+
.filter(Boolean)
|
|
257
|
+
.slice(0, 2)
|
|
258
|
+
.map((part) => part[0]?.toLocaleUpperCase())
|
|
259
|
+
.join("") || "?"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -2,11 +2,9 @@ import { getUserContext, type IDoc, type IUser, Users } from "@peers-app/peers-s
|
|
|
2
2
|
import { Input } from "../../components/input";
|
|
3
3
|
import { LoadingIndicator } from "../../components/loading-indicator";
|
|
4
4
|
import { SaveButton } from "../../components/save-button";
|
|
5
|
-
import {
|
|
5
|
+
import { Tabs } from "../../components/tabs";
|
|
6
6
|
import { TrustLevelDropdown } from "../../components/trust-level-dropdown";
|
|
7
7
|
import { usePromise } from "../../hooks";
|
|
8
|
-
import { updateActiveTabTitle } from "../../tabs-layout/tabs-state";
|
|
9
|
-
import { registerInternalPeersUI } from "../../ui-router/ui-loader";
|
|
10
8
|
|
|
11
9
|
interface IProps {
|
|
12
10
|
userId: string;
|
|
@@ -21,7 +19,6 @@ export const ContactDetails = (props: IProps) => {
|
|
|
21
19
|
if (!contact) {
|
|
22
20
|
throw new Error("Contact not found");
|
|
23
21
|
}
|
|
24
|
-
updateActiveTabTitle(contact.name || "Contact");
|
|
25
22
|
return Users(userContext.userDataContext).initDoc(contact);
|
|
26
23
|
},
|
|
27
24
|
undefined,
|
|
@@ -33,7 +30,7 @@ export const ContactDetails = (props: IProps) => {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
return (
|
|
36
|
-
<div className="container-
|
|
33
|
+
<div className="container-xxl p-3">
|
|
37
34
|
<div className="d-flex">
|
|
38
35
|
<div>
|
|
39
36
|
<h4>
|
|
@@ -61,9 +58,9 @@ export const ContactDetails = (props: IProps) => {
|
|
|
61
58
|
{
|
|
62
59
|
name: "Info",
|
|
63
60
|
content: (
|
|
64
|
-
<
|
|
61
|
+
<div>
|
|
65
62
|
<ContactInfo contact={contact} />
|
|
66
|
-
</
|
|
63
|
+
</div>
|
|
67
64
|
),
|
|
68
65
|
},
|
|
69
66
|
]}
|
|
@@ -124,22 +121,3 @@ const ContactInfo = (props: { contact: IDoc<IUser> }) => {
|
|
|
124
121
|
</div>
|
|
125
122
|
);
|
|
126
123
|
};
|
|
127
|
-
|
|
128
|
-
registerInternalPeersUI({
|
|
129
|
-
peersUIId: "00contact00details00component",
|
|
130
|
-
component: ContactDetails,
|
|
131
|
-
routes: [
|
|
132
|
-
{
|
|
133
|
-
isMatch: (props, context) => {
|
|
134
|
-
const match = context.path.match(/^contacts\/([a-zA-Z0-9]+)$/);
|
|
135
|
-
if (match) {
|
|
136
|
-
props.userId = match[1];
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
return false;
|
|
140
|
-
},
|
|
141
|
-
uiCategory: "screen",
|
|
142
|
-
priority: 2,
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { TrustLevel } from "@peers-app/peers-sdk";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { type IPersonSummary, PersonRow } from "./contact-list";
|
|
4
|
+
|
|
5
|
+
jest.mock("../../globals", () => ({
|
|
6
|
+
mainContentPath: jest.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
jest.mock("../../hooks", () => ({
|
|
10
|
+
usePromise: jest.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
jest.mock("../../ui-router/ui-loader", () => ({
|
|
14
|
+
registerInternalPeersUI: jest.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock("../identity/invite-contact-panel", () => ({
|
|
18
|
+
InviteContactPanel: () => null,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
jest.mock("../identity/profile-invite", () => ({
|
|
22
|
+
ProfileInviteSection: () => null,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
jest.mock("../invites/activity-view", () => ({
|
|
26
|
+
PendingWorkLink: () => null,
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
const person: IPersonSummary = {
|
|
30
|
+
user: {
|
|
31
|
+
userId: "abcdefghijklmnopqrstuvwxy",
|
|
32
|
+
name: "Alex",
|
|
33
|
+
publicKey: "signing-key",
|
|
34
|
+
publicBoxKey: "box-key",
|
|
35
|
+
},
|
|
36
|
+
isContact: true,
|
|
37
|
+
trustLevel: TrustLevel.Known,
|
|
38
|
+
sharedGroups: [{ groupId: "zyxwvutsrqponmlkjihgfedcb", name: "Neighbors" }],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
describe("PersonRow", () => {
|
|
42
|
+
it("shows useful relationship context without exposing an ordinary user id", () => {
|
|
43
|
+
render(<PersonRow row={person} duplicateName={false} />);
|
|
44
|
+
|
|
45
|
+
expect(screen.getByText("Alex")).toBeInTheDocument();
|
|
46
|
+
expect(screen.getByText("Contact · Shared group: Neighbors")).toBeInTheDocument();
|
|
47
|
+
expect(screen.queryByText(person.user.userId)).not.toBeInTheDocument();
|
|
48
|
+
expect(screen.getByRole("link")).toHaveAttribute(
|
|
49
|
+
"href",
|
|
50
|
+
`#identity/people/${person.user.userId}`,
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("adds only a short discriminator when names collide", () => {
|
|
55
|
+
render(<PersonRow row={person} duplicateName />);
|
|
56
|
+
|
|
57
|
+
expect(screen.getByText(`…${person.user.userId.slice(-6)}`)).toBeInTheDocument();
|
|
58
|
+
});
|
|
59
|
+
});
|