add event signature verification icon

improve QrCode on user profile
This commit is contained in:
hzrd149
2023-03-23 11:37:50 -05:00
parent 6405f475d0
commit 259e4a6146
8 changed files with 116 additions and 42 deletions

View File

@@ -11,10 +11,13 @@ import { UserLink } from "./user-link";
import { UserDnsIdentityIcon } from "./user-dns-identity"; import { UserDnsIdentityIcon } from "./user-dns-identity";
import { Bech32Prefix, normalizeToBech32 } from "../helpers/nip19"; import { Bech32Prefix, normalizeToBech32 } from "../helpers/nip19";
import { convertTimestampToDate } from "../helpers/date"; import { convertTimestampToDate } from "../helpers/date";
import useSubject from "../hooks/use-subject";
import settings from "../services/settings";
import EventVerificationIcon from "./event-verification-icon";
const EmbeddedNote = ({ note }: { note: NostrEvent }) => { const EmbeddedNote = ({ note }: { note: NostrEvent }) => {
const isMobile = useIsMobile();
const account = useCurrentAccount(); const account = useCurrentAccount();
const showSignatureVerification = useSubject(settings.showSignatureVerification);
const contacts = useUserContacts(account.pubkey); const contacts = useUserContacts(account.pubkey);
const following = contacts?.contacts || []; const following = contacts?.contacts || [];
@@ -29,7 +32,8 @@ const EmbeddedNote = ({ note }: { note: NostrEvent }) => {
<UserLink pubkey={note.pubkey} /> <UserLink pubkey={note.pubkey} />
</Heading> </Heading>
<UserDnsIdentityIcon pubkey={note.pubkey} onlyIcon /> <UserDnsIdentityIcon pubkey={note.pubkey} onlyIcon />
{!isMobile && <Flex grow={1} />} <Flex grow={1} />
{showSignatureVerification && <EventVerificationIcon event={note} />}
<Link as={RouterLink} to={`/n/${normalizeToBech32(note.id, Bech32Prefix.Note)}`} whiteSpace="nowrap"> <Link as={RouterLink} to={`/n/${normalizeToBech32(note.id, Bech32Prefix.Note)}`} whiteSpace="nowrap">
{moment(convertTimestampToDate(note.created_at)).fromNow()} {moment(convertTimestampToDate(note.created_at)).fromNow()}
</Link> </Link>

View File

@@ -0,0 +1,13 @@
import { NostrEvent } from "../types/nostr-event";
import { verifySignature } from "nostr-tools";
import { useMemo } from "react";
import { CheckIcon, VerificationFailed } from "./icons";
export default function EventVerificationIcon({ event }: { event: NostrEvent }) {
const valid = useMemo(() => verifySignature(event), [event]);
if (!valid) {
return <VerificationFailed color="red.500" />;
}
return <CheckIcon color="green.500" />;
}

View File

@@ -3,7 +3,6 @@ import { Link as RouterLink } from "react-router-dom";
import moment from "moment"; import moment from "moment";
import { import {
Box, Box,
Button,
ButtonGroup, ButtonGroup,
Card, Card,
CardBody, CardBody,
@@ -36,6 +35,7 @@ import NoteZapButton from "./note-zap-button";
import { ExpandProvider } from "./expanded"; import { ExpandProvider } from "./expanded";
import useSubject from "../../hooks/use-subject"; import useSubject from "../../hooks/use-subject";
import settings from "../../services/settings"; import settings from "../../services/settings";
import EventVerificationIcon from "../event-verification-icon";
export type NoteProps = { export type NoteProps = {
event: NostrEvent; event: NostrEvent;
@@ -47,6 +47,7 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
const account = useCurrentAccount(); const account = useCurrentAccount();
const { openModal } = useContext(PostModalContext); const { openModal } = useContext(PostModalContext);
const showReactions = useSubject(settings.showReactions); const showReactions = useSubject(settings.showReactions);
const showSignatureVerification = useSubject(settings.showSignatureVerification);
const contacts = useUserContacts(account.pubkey); const contacts = useUserContacts(account.pubkey);
const following = contacts?.contacts || []; const following = contacts?.contacts || [];
@@ -65,7 +66,8 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
<UserLink pubkey={event.pubkey} /> <UserLink pubkey={event.pubkey} />
</Heading> </Heading>
<UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon /> <UserDnsIdentityIcon pubkey={event.pubkey} onlyIcon />
{!isMobile && <Flex grow={1} />} <Flex grow={1} />
{showSignatureVerification && <EventVerificationIcon event={event} />}
<Link as={RouterLink} to={`/n/${normalizeToBech32(event.id, Bech32Prefix.Note)}`} whiteSpace="nowrap"> <Link as={RouterLink} to={`/n/${normalizeToBech32(event.id, Bech32Prefix.Note)}`} whiteSpace="nowrap">
{moment(convertTimestampToDate(event.created_at)).fromNow()} {moment(convertTimestampToDate(event.created_at)).fromNow()}
</Link> </Link>

View File

@@ -1,31 +0,0 @@
import {
IconButton,
IconButtonProps,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
ModalHeader,
useDisclosure,
} from "@chakra-ui/react";
import { QrCodeIcon } from "./icons";
import QrCodeSvg from "./qr-code-svg";
export const QrIconButton = ({ content, ...props }: { content: string } & Omit<IconButtonProps, "icon">) => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<IconButton icon={<QrCodeIcon />} onClick={onOpen} {...props} />
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalBody>
<QrCodeSvg content={content} border={2} />
</ModalBody>
</ModalContent>
</Modal>
</>
);
};

View File

@@ -7,6 +7,7 @@ const settings = {
autoShowMedia: new PersistentSubject(true), autoShowMedia: new PersistentSubject(true),
proxyUserMedia: new PersistentSubject(false), proxyUserMedia: new PersistentSubject(false),
showReactions: new PersistentSubject(true), showReactions: new PersistentSubject(true),
showSignatureVerification: new PersistentSubject(false),
accounts: new PersistentSubject<Account[]>([]), accounts: new PersistentSubject<Account[]>([]),
}; };

View File

@@ -26,6 +26,7 @@ export default function SettingsView() {
const autoShowMedia = useSubject(settings.autoShowMedia); const autoShowMedia = useSubject(settings.autoShowMedia);
const proxyUserMedia = useSubject(settings.proxyUserMedia); const proxyUserMedia = useSubject(settings.proxyUserMedia);
const showReactions = useSubject(settings.showReactions); const showReactions = useSubject(settings.showReactions);
const showSignatureVerification = useSubject(settings.showSignatureVerification);
const { colorMode, setColorMode } = useColorMode(); const { colorMode, setColorMode } = useColorMode();
@@ -129,9 +130,9 @@ export default function SettingsView() {
/> />
</Flex> </Flex>
<FormHelperText> <FormHelperText>
<span>Enabled: media.nostr.band is used to get smaller of profile images (saves ~50Mb of data)</span> <span>Enabled: Use media.nostr.band to get smaller profile pictures (saves ~50Mb of data)</span>
<br /> <br />
<span>Side Effect: some user pictures may not load or may be outdated</span> <span>Side Effect: Some user pictures may not load or may be outdated</span>
</FormHelperText> </FormHelperText>
</FormControl> </FormControl>
<FormControl> <FormControl>
@@ -145,12 +146,12 @@ export default function SettingsView() {
onChange={(v) => settings.autoShowMedia.next(v.target.checked)} onChange={(v) => settings.autoShowMedia.next(v.target.checked)}
/> />
</Flex> </Flex>
<FormHelperText>Disabled: images and videos will show expandable buttons.</FormHelperText> <FormHelperText>Disabled: Images and videos will show expandable buttons</FormHelperText>
</FormControl> </FormControl>
<FormControl> <FormControl>
<Flex alignItems="center"> <Flex alignItems="center">
<FormLabel htmlFor="show-reactions" mb="0"> <FormLabel htmlFor="show-reactions" mb="0">
Show Reactions Show reactions
</FormLabel> </FormLabel>
<Switch <Switch
id="show-reactions" id="show-reactions"
@@ -158,7 +159,20 @@ export default function SettingsView() {
onChange={(v) => settings.showReactions.next(v.target.checked)} onChange={(v) => settings.showReactions.next(v.target.checked)}
/> />
</Flex> </Flex>
<FormHelperText>Enabled: show reactions on notes.</FormHelperText> <FormHelperText>Enabled: Show reactions on notes</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="show-sig-verify" mb="0">
Show signature verification
</FormLabel>
<Switch
id="show-sig-verify"
isChecked={showSignatureVerification}
onChange={(v) => settings.showSignatureVerification.next(v.target.checked)}
/>
</Flex>
<FormHelperText>Enabled: show signature verification on notes</FormHelperText>
</FormControl> </FormControl>
</Flex> </Flex>
</AccordionPanel> </AccordionPanel>

View File

@@ -5,7 +5,7 @@ import { useNavigate, Link as RouterLink } from "react-router-dom";
import { RelayMode } from "../../../classes/relay"; import { RelayMode } from "../../../classes/relay";
import { CopyIconButton } from "../../../components/copy-icon-button"; import { CopyIconButton } from "../../../components/copy-icon-button";
import { ChatIcon, ExternalLinkIcon, KeyIcon, SettingsIcon } from "../../../components/icons"; import { ChatIcon, ExternalLinkIcon, KeyIcon, SettingsIcon } from "../../../components/icons";
import { QrIconButton } from "../../../components/qr-icon-button"; import { QrIconButton } from "./share-qr-button";
import { UserAvatar } from "../../../components/user-avatar"; import { UserAvatar } from "../../../components/user-avatar";
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity"; import { UserDnsIdentityIcon } from "../../../components/user-dns-identity";
import { UserFollowButton } from "../../../components/user-follow-button"; import { UserFollowButton } from "../../../components/user-follow-button";
@@ -75,7 +75,7 @@ export default function Header({ pubkey }: { pubkey: string }) {
<KeyIcon /> <KeyIcon />
<Text>{truncatedId(npub, 10)}</Text> <Text>{truncatedId(npub, 10)}</Text>
<CopyIconButton text={npub} title="Copy npub" aria-label="Copy npub" size="xs" /> <CopyIconButton text={npub} title="Copy npub" aria-label="Copy npub" size="xs" />
<QrIconButton content={"nostr:" + shareLink} title="Show QrCode" aria-label="Show QrCode" size="xs" /> <QrIconButton pubkey={pubkey} title="Show QrCode" aria-label="Show QrCode" size="xs" />
</Flex> </Flex>
)} )}
<Flex gap="2" ml="auto"> <Flex gap="2" ml="auto">

View File

@@ -0,0 +1,71 @@
import {
IconButton,
IconButtonProps,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
useDisclosure,
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
} from "@chakra-ui/react";
import { useMemo } from "react";
import { RelayMode } from "../../../classes/relay";
import { QrCodeIcon } from "../../../components/icons";
import QrCodeSvg from "../../../components/qr-code-svg";
import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip19";
import useFallbackUserRelays from "../../../hooks/use-fallback-user-relays";
import relayScoreboardService from "../../../services/relay-scoreboard";
import { nip19 } from "nostr-tools";
function useUserShareLink(pubkey: string) {
const userRelays = useFallbackUserRelays(pubkey);
return useMemo(() => {
const writeUrls = userRelays.filter((r) => r.mode & RelayMode.WRITE).map((r) => r.url);
const ranked = relayScoreboardService.getRankedRelays(writeUrls);
const onlyTwo = ranked.slice(0, 2);
return onlyTwo.length > 0 ? nip19.nprofileEncode({ pubkey, relays: onlyTwo }) : nip19.npubEncode(pubkey);
}, [userRelays]);
}
export const QrIconButton = ({ pubkey, ...props }: { pubkey: string } & Omit<IconButtonProps, "icon">) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey) || pubkey;
const nprofile = useUserShareLink(pubkey);
return (
<>
<IconButton icon={<QrCodeIcon />} onClick={onOpen} {...props} />
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalCloseButton />
<ModalBody p="2">
<Tabs>
<TabList>
<Tab>npub</Tab>
<Tab>nprofile</Tab>
</TabList>
<TabPanels>
<TabPanel>
<QrCodeSvg content={"nostr:" + npub} border={2} />
</TabPanel>
<TabPanel>
<QrCodeSvg content={"nostr:" + nprofile} border={2} />
</TabPanel>
</TabPanels>
</Tabs>
</ModalBody>
</ModalContent>
</Modal>
</>
);
};