Skip to content

Commit

Permalink
client-web: fixes related to reading account from QR; add options to …
Browse files Browse the repository at this point in the history
…copy event and user link
  • Loading branch information
franzos committed Sep 29, 2023
1 parent e951a62 commit 53f4cd6
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 57 deletions.
11 changes: 11 additions & 0 deletions client-web/src/components/event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
UserBase,
extractEventContent,
NOSTR_URL_PREFIX,
encodeBech32,
BECH32_PREFIX,
} from "@nostr-ts/common";
import { useNClient } from "../state/client";
import { excerpt } from "../lib/excerpt";
Expand Down Expand Up @@ -51,6 +53,13 @@ export function Event({ data, level }: EventProps) {
const content = extractEventContent(data.event.content);
const contentWarning = eventHasContentWarning(data.event);

const nevent = encodeBech32(BECH32_PREFIX.Event, [
{
type: 0,
value: data.event.id,
},
]);

let visible;
if (content?.text && !contentWarning) {
visible = true;
Expand Down Expand Up @@ -269,6 +278,7 @@ export function Event({ data, level }: EventProps) {
<EventCardFooter
isReady={isReady}
level={level}
nEventString={nevent}
createdAt={data.event.created_at}
repliesCount={data.repliesCount}
reactionsCount={data.reactionsCount}
Expand All @@ -295,6 +305,7 @@ export function Event({ data, level }: EventProps) {
)}
<EventInfoModal
data={data}
nEventString={nevent}
isOpen={isInfoModalOpen}
onClose={onInfoModalClose}
/>
Expand Down
60 changes: 48 additions & 12 deletions client-web/src/components/event/card-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,26 @@ import {
Box,
Text,
VStack,
Menu,
MenuButton,
MenuItem,
MenuList,
useToast,
} from "@chakra-ui/react";
import { ReactionsCount } from "@nostr-ts/common";
import InformationOutlineIcon from "mdi-react/InformationOutlineIcon";
import { filterReactions } from "../../lib/event-reactions-filter";
import { unixTimeToRelative } from "../../lib/relative-time";
import { EventActionButtons } from "./action-buttons";
import CodeJsonIcon from "mdi-react/CodeJsonIcon";
import ContentCopyIcon from "mdi-react/ContentCopyIcon";
import { excerpt } from "../../lib/excerpt";

export interface CardFooterProps {
isReady: boolean;

level: number;
nEventString: string;
createdAt: number;
repliesCount: number;
reactionsCount: ReactionsCount;
Expand All @@ -40,8 +49,9 @@ export interface CardFooterProps {
export const EventCardFooter = ({
isReady,

createdAt,
level,
nEventString,
createdAt,
repliesCount,
reactionsCount,
repostCount,
Expand All @@ -58,8 +68,20 @@ export const EventCardFooter = ({

onAction,
}: CardFooterProps) => {
const toast = useToast();
const reactions = filterReactions(reactionsCount);

const copyEventLinkToClipboard = () => {
const url = `${window.location.origin}/#/e/${nEventString}`;
navigator.clipboard.writeText(url);
toast({
description: `Copied ${excerpt(url, 40)} to clipboard`,
status: "success",
duration: 5000,
isClosable: true,
});
};

return (
<CardFooter p={1} pl={2} pr={2}>
<VStack align="stretch">
Expand Down Expand Up @@ -93,21 +115,35 @@ export const EventCardFooter = ({
onReplyClose={onReplyClose}
onAction={onAction}
/>

<Spacer />
<Text fontSize={12} color="gray.500">
{unixTimeToRelative(createdAt)}
</Text>
<IconButton
aria-label="Event info"
size={"xs"}
variant="outline"
color="gray.500"
icon={<Icon as={InformationOutlineIcon} />}
onClick={() =>
isInfoModalOpen ? onInfoModalClose() : onInfoModalOpen()
}
></IconButton>
<Menu>
<MenuButton
as={IconButton}
size="xs"
variant="outline"
color="gray.500"
icon={<Icon as={InformationOutlineIcon} />}
/>
<MenuList>
<MenuItem
icon={<Icon as={ContentCopyIcon} />}
onClick={copyEventLinkToClipboard}
>
Copy direct event link
</MenuItem>
<MenuItem
icon={<Icon as={CodeJsonIcon} />}
onClick={() =>
isInfoModalOpen ? onInfoModalClose() : onInfoModalOpen()
}
>
Event JSON
</MenuItem>
</MenuList>
</Menu>
</HStack>
</Box>
</VStack>
Expand Down
21 changes: 9 additions & 12 deletions client-web/src/components/event/info-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,23 @@ import {
Text,
Link,
} from "@chakra-ui/react";
import {
BECH32_PREFIX,
LightProcessedEvent,
encodeBech32,
} from "@nostr-ts/common";
import { LightProcessedEvent } from "@nostr-ts/common";
import { Link as RouterLink } from "react-router-dom";

interface EventInfoModalProps {
data: LightProcessedEvent;
nEventString: string;
isOpen: boolean;
onClose: () => void;
}

export function EventInfoModal({ data, isOpen, onClose }: EventInfoModalProps) {
const eventLink = `/e/${encodeBech32(BECH32_PREFIX.NoteIDs, [
{
type: 0,
value: data.event.id,
},
])}`;
export function EventInfoModal({
data,
nEventString,
isOpen,
onClose,
}: EventInfoModalProps) {
const eventLink = `${window.location.origin}/e/${nEventString}`;

const relay = data.eventRelayUrls
? data.eventRelayUrls[0]
Expand Down
76 changes: 76 additions & 0 deletions client-web/src/components/qrcode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { BECH32_PREFIX, encodeBech32 } from "@nostr-ts/common";
import {
Icon,
IconButton,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
useDisclosure,
} from "@chakra-ui/react";
import { QRCodeSVG } from "qrcode.react";
import QrcodeIcon from "mdi-react/QrcodeIcon";

interface QRCodeProps {
kind:
| BECH32_PREFIX.PublicKeys
| BECH32_PREFIX.PrivateKeys
| BECH32_PREFIX.Profile;
value: string;
size?: "xs" | "sm" | "md" | "lg" | "xl";
}

const labels = [
{
kind: BECH32_PREFIX.PublicKeys,
label: "Public Key",
},
{
kind: BECH32_PREFIX.PrivateKeys,
label: "Private Key",
},
{
kind: BECH32_PREFIX.Profile,
label: "Profile",
},
];

export function QRCodeModal({ kind, value, size }: QRCodeProps) {
("");
const { isOpen, onOpen, onClose } = useDisclosure();

const label = labels.find((l) => l.kind === kind)?.label || "Unknown";
const tlvItems = [
{
type: 0,
value,
},
];
const encoded = encodeBech32(kind, tlvItems);

return (
<>
<IconButton
icon={<Icon as={QrcodeIcon} />}
onClick={isOpen ? onClose : onOpen}
aria-label="QR"
size={size || "md"}
/>

<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent width={380}>
<ModalHeader>{label}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<QRCodeSVG value={encoded} size={330} />
<Input value={encoded} mt={2} isReadOnly />
</ModalBody>
</ModalContent>
</Modal>
</>
);
}
27 changes: 27 additions & 0 deletions client-web/src/components/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
PopoverCloseButton,
PopoverContent,
PopoverTrigger,
useToast,
} from "@chakra-ui/react";
import { Link } from "react-router-dom";
import { useNClient } from "../state/client";
Expand All @@ -28,10 +29,13 @@ import CancelIcon from "mdi-react/CancelIcon";
import AccountMultipleIcon from "mdi-react/AccountMultipleIcon";
import PlaylistEditIcon from "mdi-react/PlaylistEditIcon";
import DotsVerticalCircleOutlineIcon from "mdi-react/DotsVerticalCircleOutlineIcon";
import ContentCopyIcon from "mdi-react/ContentCopyIcon";
import { ListAssignmentModal } from "./list-assignment-modal";
import { useEffect, useState } from "react";
import Avatar from "boring-avatars";
import { ZapModal } from "./event/zap-modal";
import { QRCodeModal } from "./qrcode";
import { excerpt } from "../lib/excerpt";

export function User({
user: { pubkey, data },
Expand All @@ -45,6 +49,8 @@ export function User({
isBlocked,
},
}: UserInfoProps) {
const toast = useToast();

const name = data && data.name ? data.name : "Anonymous";
const displayName = data && data.display_name ? data.display_name : name;
const picture = data && data.picture ? data.picture : "";
Expand All @@ -69,6 +75,17 @@ export function User({
setProfileLink(`/p/${npub}`);
}, [pubkey]);

const copyUserLinkToClipboard = () => {
const url = `${window.location.origin}/#${profileLink}`;
navigator.clipboard.writeText(url);
toast({
description: `Copied ${excerpt(url, 40)} to clipboard`,
status: "success",
duration: 5000,
isClosable: true,
});
};

const loadFollowingStatus = async () => {
const following = await useNClient.getState().followingUser(pubkey);
setFollowing(following);
Expand Down Expand Up @@ -139,10 +156,13 @@ export function User({

<Spacer />

<QRCodeModal kind={BECH32_PREFIX.Profile} value={pubkey} size="xs" />
<Menu isLazy onOpen={loadFollowingStatus}>
<MenuButton
as={IconButton}
size="xs"
variant="outline"
color="gray.500"
icon={<Icon as={DotsVerticalCircleOutlineIcon} />}
>
Actions
Expand Down Expand Up @@ -181,6 +201,13 @@ export function User({
{following ? "Unfollow" : "Follow"}
</MenuItem>
)}

<MenuItem
icon={<Icon as={ContentCopyIcon} />}
onClick={copyUserLinkToClipboard}
>
Copy direct profile link
</MenuItem>
</MenuList>
</Menu>
</HStack>
Expand Down
Loading

0 comments on commit 53f4cd6

Please sign in to comment.