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 { Bech32Prefix, normalizeToBech32 } from "../helpers/nip19";
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 isMobile = useIsMobile();
const account = useCurrentAccount();
const showSignatureVerification = useSubject(settings.showSignatureVerification);
const contacts = useUserContacts(account.pubkey);
const following = contacts?.contacts || [];
@ -29,7 +32,8 @@ const EmbeddedNote = ({ note }: { note: NostrEvent }) => {
<UserLink pubkey={note.pubkey} />
</Heading>
<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">
{moment(convertTimestampToDate(note.created_at)).fromNow()}
</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 {
Box,
Button,
ButtonGroup,
Card,
CardBody,
@ -36,6 +35,7 @@ import NoteZapButton from "./note-zap-button";
import { ExpandProvider } from "./expanded";
import useSubject from "../../hooks/use-subject";
import settings from "../../services/settings";
import EventVerificationIcon from "../event-verification-icon";
export type NoteProps = {
event: NostrEvent;
@ -47,6 +47,7 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
const account = useCurrentAccount();
const { openModal } = useContext(PostModalContext);
const showReactions = useSubject(settings.showReactions);
const showSignatureVerification = useSubject(settings.showSignatureVerification);
const contacts = useUserContacts(account.pubkey);
const following = contacts?.contacts || [];
@ -65,7 +66,8 @@ export const Note = React.memo(({ event, maxHeight, variant = "outline" }: NoteP
<UserLink pubkey={event.pubkey} />
</Heading>
<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">
{moment(convertTimestampToDate(event.created_at)).fromNow()}
</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),
proxyUserMedia: new PersistentSubject(false),
showReactions: new PersistentSubject(true),
showSignatureVerification: new PersistentSubject(false),
accounts: new PersistentSubject<Account[]>([]),
};

View File

@ -26,6 +26,7 @@ export default function SettingsView() {
const autoShowMedia = useSubject(settings.autoShowMedia);
const proxyUserMedia = useSubject(settings.proxyUserMedia);
const showReactions = useSubject(settings.showReactions);
const showSignatureVerification = useSubject(settings.showSignatureVerification);
const { colorMode, setColorMode } = useColorMode();
@ -129,9 +130,9 @@ export default function SettingsView() {
/>
</Flex>
<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 />
<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>
</FormControl>
<FormControl>
@ -145,12 +146,12 @@ export default function SettingsView() {
onChange={(v) => settings.autoShowMedia.next(v.target.checked)}
/>
</Flex>
<FormHelperText>Disabled: images and videos will show expandable buttons.</FormHelperText>
<FormHelperText>Disabled: Images and videos will show expandable buttons</FormHelperText>
</FormControl>
<FormControl>
<Flex alignItems="center">
<FormLabel htmlFor="show-reactions" mb="0">
Show Reactions
Show reactions
</FormLabel>
<Switch
id="show-reactions"
@ -158,7 +159,20 @@ export default function SettingsView() {
onChange={(v) => settings.showReactions.next(v.target.checked)}
/>
</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>
</Flex>
</AccordionPanel>

View File

@ -5,7 +5,7 @@ import { useNavigate, Link as RouterLink } from "react-router-dom";
import { RelayMode } from "../../../classes/relay";
import { CopyIconButton } from "../../../components/copy-icon-button";
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 { UserDnsIdentityIcon } from "../../../components/user-dns-identity";
import { UserFollowButton } from "../../../components/user-follow-button";
@ -75,7 +75,7 @@ export default function Header({ pubkey }: { pubkey: string }) {
<KeyIcon />
<Text>{truncatedId(npub, 10)}</Text>
<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 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>
</>
);
};