add qr code scanners to signin view

This commit is contained in:
hzrd149 2024-02-02 10:48:30 +00:00
parent 006971409c
commit 59e61844a8
8 changed files with 85 additions and 35 deletions

View File

@ -0,0 +1,21 @@
import { IconButton, Spinner, useDisclosure } from "@chakra-ui/react";
import { type QrScannerModalProps } from "./qr-scanner-modal";
import { QrCodeIcon } from "./icons";
import { Suspense, lazy } from "react";
const QrScannerModal = lazy(() => import("./qr-scanner-modal"));
export default function QRCodeScannerButton({ onData }: { onData: QrScannerModalProps["onData"] }) {
const modal = useDisclosure();
return (
<>
<IconButton onClick={modal.onOpen} icon={<QrCodeIcon boxSize={6} />} aria-label="Qr Scanner" />
{modal.isOpen && (
<Suspense fallback={null}>
<QrScannerModal isOpen={modal.isOpen} onClose={modal.onClose} onData={onData} />
</Suspense>
)}
</>
);
}

View File

@ -2,11 +2,8 @@ import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalOverlay, Moda
import { useState } from "react";
import BarcodeScannerComponent from "react-qr-barcode-scanner";
export default function QrScannerModal({
isOpen,
onClose,
onData,
}: { onData: (text: string) => void } & Pick<ModalProps, "isOpen" | "onClose">) {
export type QrScannerModalProps = { onData: (text: string) => void } & Pick<ModalProps, "isOpen" | "onClose">;
export default function QrScannerModal({ isOpen, onClose, onData }: QrScannerModalProps) {
const [stopStream, setStopStream] = useState(false);
const handleClose = () => {
// Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later
@ -18,7 +15,7 @@ export default function QrScannerModal({
<Modal isOpen={isOpen} onClose={handleClose}>
<ModalOverlay />
<ModalContent>
<ModalBody>
<ModalBody p="2">
<BarcodeScannerComponent
stopStream={stopStream}
onUpdate={(err, result) => {
@ -31,7 +28,7 @@ export default function QrScannerModal({
/>
</ModalBody>
<ModalFooter>
<ModalFooter px="2" pb="2" pt="0">
<Button onClick={handleClose}>Cancel</Button>
</ModalFooter>
</ModalContent>

View File

@ -1,13 +1,11 @@
import { useCallback, useEffect, useState } from "react";
import { Button, ButtonGroup, Flex, IconButton, Input, Link, useDisclosure } from "@chakra-ui/react";
import { Button, ButtonGroup, Flex, IconButton, Input, Link } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import { SEARCH_RELAYS } from "../../const";
import { safeDecode } from "../../helpers/nip19";
import { getMatchHashtag } from "../../helpers/regexp";
import { CommunityIcon, CopyToClipboardIcon, NotesIcon, QrCodeIcon } from "../../components/icons";
import QrScannerModal from "../../components/qr-scanner-modal";
import RelaySelectionButton from "../../components/relay-selection/relay-selection-button";
import { CommunityIcon, CopyToClipboardIcon, NotesIcon } from "../../components/icons";
import RelaySelectionProvider from "../../providers/local/relay-selection-provider";
import VerticalPageLayout from "../../components/vertical-page-layout";
import User01 from "../../components/icons/user-01";
@ -20,10 +18,10 @@ import PeopleListProvider from "../../providers/local/people-list-provider";
import PeopleListSelection from "../../components/people-list-selection/people-list-selection";
import useRouteSearchValue from "../../hooks/use-route-search-value";
import { useBreakpointValue } from "../../providers/global/breakpoint-provider";
import QRCodeScannerButton from "../../components/qr-code-scanner-button";
export function SearchPage() {
const navigate = useNavigate();
const qrScannerModal = useDisclosure();
const autoFocusSearch = useBreakpointValue({ base: false, lg: true });
@ -82,12 +80,10 @@ export function SearchPage() {
return (
<VerticalPageLayout>
<QrScannerModal isOpen={qrScannerModal.isOpen} onClose={qrScannerModal.onClose} onData={handleSearchText} />
<form onSubmit={handleSubmit}>
<Flex gap="2" wrap="wrap">
<Flex gap="2" grow={1}>
<IconButton onClick={qrScannerModal.onOpen} icon={<QrCodeIcon />} aria-label="Qr Scanner" />
<QRCodeScannerButton onData={handleSearchText} />
{!!navigator.clipboard?.readText && (
<IconButton onClick={readClipboard} icon={<CopyToClipboardIcon />} aria-label="Read clipboard" />
)}

View File

@ -1,5 +1,10 @@
import { useState } from "react";
import { useMemo, useState } from "react";
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Button,
Card,
CardBody,
@ -9,6 +14,7 @@ import {
Input,
InputGroup,
InputRightAddon,
Link,
LinkBox,
Tag,
Text,
@ -28,6 +34,7 @@ import { useUserMetadata } from "../../../hooks/use-user-metadata";
import nostrConnectService from "../../../services/nostr-connect";
import accountService from "../../../services/account";
import { safeRelayUrls } from "../../../helpers/relay";
import { safeJson } from "../../../helpers/parse";
function ProviderCard({ onClick, provider }: { onClick: () => void; provider: NostrEvent }) {
const metadata = JSON.parse(provider.content) as Kind0ParsedContent;
@ -68,6 +75,10 @@ export default function LoginNostrAddressCreate() {
const providers = useNip05Providers();
const [selected, setSelected] = useState<NostrEvent>();
const userMetadata = useUserMetadata(selected?.pubkey);
const providerMetadata = useMemo<Kind0ParsedContent | undefined>(
() => selected && safeJson(selected.content, undefined),
[selected],
);
const createAccount: React.FormEventHandler<HTMLDivElement> = async (e) => {
e.preventDefault();
@ -75,7 +86,6 @@ export default function LoginNostrAddressCreate() {
try {
setLoading("Creating...");
const providerMetadata = JSON.parse(selected.content) as Kind0ParsedContent;
const metadata: Kind0ParsedContent = { ...userMetadata, ...providerMetadata };
if (!metadata.nip05) throw new Error("Provider missing nip05 address");
const nip05 = await dnsIdentityService.fetchIdentity(metadata.nip05);
@ -109,10 +119,15 @@ export default function LoginNostrAddressCreate() {
return (
<>
<Flex gap="2" alignItems="center">
<MetadataAvatar metadata={metadata} size="sm" />
<Heading size="sm">{metadata.displayName || metadata.name}</Heading>
</Flex>
<div>
<Flex gap="2" alignItems="center">
<MetadataAvatar metadata={metadata} size="sm" />
<Heading size="sm">{metadata.displayName || metadata.name}</Heading>
</Flex>
<Link href={providerMetadata?.website || userMetadata?.website} isExternal color="blue.500">
{providerMetadata?.website || userMetadata?.website}
</Link>
</div>
<InputGroup>
<Input name="name" isRequired value={name} onChange={(e) => setName(e.target.value)} />
<InputRightAddon as="button" onClick={() => setSelected(undefined)} cursor="pointer">

View File

@ -10,6 +10,7 @@ import accountService from "../../../services/account";
import { COMMON_CONTACT_RELAY } from "../../../const";
import { safeRelayUrls } from "../../../helpers/relay";
import { getMatchSimpleEmail } from "../../../helpers/regexp";
import QRCodeScannerButton from "../../../components/qr-code-scanner-button";
export default function LoginNostrAddressView() {
const navigate = useNavigate();
@ -124,6 +125,7 @@ export default function LoginNostrAddressView() {
onChange={(e) => setAddress(e.target.value)}
autoComplete="off"
/>
<QRCodeScannerButton onData={(v) => setAddress(v)} />
</Flex>
</FormControl>
{renderStatus()}

View File

@ -1,9 +1,20 @@
import { useState } from "react";
import { Button, Flex, FormControl, FormHelperText, FormLabel, Input, Text, useToast } from "@chakra-ui/react";
import {
Button,
Flex,
FormControl,
FormHelperText,
FormLabel,
IconButton,
Input,
Text,
useToast,
} from "@chakra-ui/react";
import { useNavigate } from "react-router-dom";
import accountService from "../../services/account";
import nostrConnectService, { NostrConnectClient } from "../../services/nostr-connect";
import QRCodeScannerButton from "../../components/qr-code-scanner-button";
export default function LoginNostrConnectView() {
const navigate = useNavigate();
@ -42,15 +53,19 @@ export default function LoginNostrConnectView() {
{loading && <Text fontSize="lg">{loading}</Text>}
{!loading && (
<FormControl>
<FormLabel>Connect URI</FormLabel>
<Input
name="nostr-address"
placeholder="bunker://<pubkey>?relay=wss://relay.example.com"
isRequired
value={uri}
onChange={(e) => setUri(e.target.value)}
autoComplete="off"
/>
<FormLabel htmlFor="input">Connect URI</FormLabel>
<Flex gap="2">
<Input
id="nostr-connect"
name="nostr-connect"
placeholder="bunker://<pubkey>?relay=wss://relay.example.com"
isRequired
value={uri}
onChange={(e) => setUri(e.target.value)}
autoComplete="off"
/>
<QRCodeScannerButton onData={(v) => setUri(v)} />
</Flex>
<FormHelperText>A bunker connect URI</FormHelperText>
</FormControl>
)}

View File

@ -6,6 +6,7 @@ import { RelayUrlInput } from "../../components/relay-url-input";
import { normalizeToHexPubkey } from "../../helpers/nip19";
import accountService from "../../services/account";
import { COMMON_CONTACT_RELAY } from "../../const";
import QRCodeScannerButton from "../../components/qr-code-scanner-button";
export default function LoginNpubView() {
const navigate = useNavigate();
@ -31,7 +32,10 @@ export default function LoginNpubView() {
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit} w="full">
<FormControl>
<FormLabel>Enter user npub</FormLabel>
<Input type="text" placeholder="npub1" isRequired value={npub} onChange={(e) => setNpub(e.target.value)} />
<Flex gap="2">
<Input type="text" placeholder="npub1" isRequired value={npub} onChange={(e) => setNpub(e.target.value)} />
<QRCodeScannerButton onData={(v) => setNpub(v)} />
</Flex>
<FormHelperText>
Enter any npub you want.{" "}
<Link isExternal href="https://nostr.directory" color="blue.500" target="_blank">

View File

@ -1,5 +1,5 @@
import { useState } from "react";
import { Center } from "@chakra-ui/react";
import { Flex } from "@chakra-ui/react";
import { useNavigate, useParams } from "react-router-dom";
import { Kind0ParsedContent } from "../../helpers/user-metadata";
@ -72,8 +72,8 @@ export default function SignupView() {
};
return (
<Center w="full" h="full">
<Flex direction="column" alignItems="center" gap="2" w="full" px="4" py="10">
{renderStep()}
</Center>
</Flex>
);
}