mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-10 04:39:19 +02:00
add event signature verification icon
improve QrCode on user profile
This commit is contained in:
parent
6405f475d0
commit
259e4a6146
@ -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>
|
||||
|
13
src/components/event-verification-icon.tsx
Normal file
13
src/components/event-verification-icon.tsx
Normal 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" />;
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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[]>([]),
|
||||
};
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
71
src/views/user/components/share-qr-button.tsx
Normal file
71
src/views/user/components/share-qr-button.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user