(feat): improve profile popup in the navbar, add shared code to that popup
This commit is contained in:
parent
7a7ab3ad2e
commit
f4088691c5
4 changed files with 114 additions and 14 deletions
|
|
@ -1,8 +1,62 @@
|
||||||
import type { User } from "@tensamin/user/context";
|
import type { User } from "@tensamin/user/context";
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@tensamin/ui";
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarImage,
|
||||||
|
Button,
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@tensamin/ui";
|
||||||
|
import { toLossySixDigitCode } from "@tensamin/shared/code";
|
||||||
import Text from "@tensamin/markdown/text";
|
import Text from "@tensamin/markdown/text";
|
||||||
|
import { ChevronDown, ChevronUp, Info } from "lucide-react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useCrypto } from "@tensamin/crypto/context";
|
||||||
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
|
import { useUser } from "@tensamin/user/context";
|
||||||
|
|
||||||
export default function Profile({ user }: { user: User }) {
|
export default function Profile({ user }: { user: User }) {
|
||||||
|
const [showAdvancedInformation, setShowAdvancedInformation] = useState(false);
|
||||||
|
const [sharedSecret, setSharedSecret] = useState("");
|
||||||
|
|
||||||
|
const { getSharedSecret } = useCrypto();
|
||||||
|
const { load } = useStorage();
|
||||||
|
const { get } = useUser();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
const ownId = await load("user_id");
|
||||||
|
const privateKey = await load("private_key");
|
||||||
|
const ownData = await get(ownId);
|
||||||
|
const secret = await getSharedSecret(
|
||||||
|
privateKey,
|
||||||
|
ownData.public_key,
|
||||||
|
user.public_key,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
setSharedSecret(secret);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
if (active) {
|
||||||
|
setSharedSecret("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, [get, getSharedSecret, load, user.public_key]);
|
||||||
|
|
||||||
|
const sharedSecretCode = sharedSecret
|
||||||
|
? toLossySixDigitCode(sharedSecret)
|
||||||
|
: "------";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
|
|
@ -18,17 +72,42 @@ export default function Profile({ user }: { user: User }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Text value={user.about || ""} />
|
<Text value={user.about || ""} />
|
||||||
<div className="flex flex-col">
|
<Button
|
||||||
<p className="text-muted-foreground text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
className="h-auto justify-start gap-1.5 px-0 py-1 text-base font-medium text-white no-underline hover:no-underline"
|
||||||
iota: {user.iota_id}
|
variant="link"
|
||||||
</p>
|
aria-expanded={showAdvancedInformation}
|
||||||
<p className="text-muted-foreground text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
onClick={() => setShowAdvancedInformation((show) => !show)}
|
||||||
user: {user.user_id}
|
>
|
||||||
</p>
|
{showAdvancedInformation ? (
|
||||||
<p className="text-muted-foreground text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
<ChevronUp className="size-4" />
|
||||||
pub key: {user.public_key}
|
) : (
|
||||||
</p>
|
<ChevronDown className="size-4" />
|
||||||
</div>
|
)}
|
||||||
|
<span>Show advanced information</span>
|
||||||
|
</Button>
|
||||||
|
{showAdvancedInformation && (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<p className="text-white flex gap-1 overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
Shared Code: <code>{sharedSecretCode}</code>{" "}
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger render={<Info className="size-3.5" />} />
|
||||||
|
<TooltipContent>
|
||||||
|
You can compare this code with the conversation partner to
|
||||||
|
validate that this chat is E2EE
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
Iota ID: <code>{user.iota_id}</code>
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
User ID: <code>{user.user_id}</code>
|
||||||
|
</p>
|
||||||
|
<p className="text-muted-foreground text-xs overflow-hidden text-ellipsis whitespace-nowrap">
|
||||||
|
Public Key: <code>{user.public_key}</code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
userId={id}
|
userId={id}
|
||||||
component={(user) =>
|
component={(user) =>
|
||||||
isMobile ? (
|
isMobile ? (
|
||||||
<p className="font-medium text-md">{user?.display}</p>
|
<p className="font-medium text-[1.07rem]">{user?.display}</p>
|
||||||
) : (
|
) : (
|
||||||
<Popover open={userInfoOpen} onOpenChange={setUserInfoOpen}>
|
<Popover open={userInfoOpen} onOpenChange={setUserInfoOpen}>
|
||||||
<PopoverTrigger
|
<PopoverTrigger
|
||||||
|
|
@ -86,7 +86,7 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) {
|
||||||
textDecorationLine: "none",
|
textDecorationLine: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<p className="font-medium text-md">{user?.display}</p>
|
<p className="font-medium text-[1.07rem]">{user?.display}</p>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
"./code": "./src/code.ts",
|
||||||
"./data": "./src/data.ts",
|
"./data": "./src/data.ts",
|
||||||
"./desktopMedia": "./src/desktopMedia.tsx",
|
"./desktopMedia": "./src/desktopMedia.tsx",
|
||||||
"./log": "./src/log.tsx",
|
"./log": "./src/log.tsx",
|
||||||
|
|
|
||||||
20
packages/shared/src/code.ts
Normal file
20
packages/shared/src/code.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export function toLossySixDigitCode(input: string): string {
|
||||||
|
let firstHash = 0xdeadbeef;
|
||||||
|
let secondHash = 0x41c6ce57;
|
||||||
|
|
||||||
|
for (let index = 0; index < input.length; index += 1) {
|
||||||
|
const character = input.charCodeAt(index);
|
||||||
|
|
||||||
|
firstHash = Math.imul(firstHash ^ character, 2654435761);
|
||||||
|
secondHash = Math.imul(secondHash ^ character, 1597334677);
|
||||||
|
}
|
||||||
|
|
||||||
|
firstHash = Math.imul(firstHash ^ (firstHash >>> 16), 2246822507);
|
||||||
|
firstHash ^= Math.imul(secondHash ^ (secondHash >>> 13), 3266489909);
|
||||||
|
secondHash = Math.imul(secondHash ^ (secondHash >>> 16), 2246822507);
|
||||||
|
secondHash ^= Math.imul(firstHash ^ (firstHash >>> 13), 3266489909);
|
||||||
|
|
||||||
|
const hash = 4294967296 * (2097151 & secondHash) + (firstHash >>> 0);
|
||||||
|
|
||||||
|
return String(hash % 1_000_000).padStart(6, "0");
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue