mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-10-05 17:55:01 +02:00
add npub login for fun
This commit is contained in:
@@ -29,7 +29,7 @@
|
|||||||
- [ ] [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md): Encrypted Direct Message
|
- [ ] [NIP-04](https://github.com/nostr-protocol/nips/blob/master/04.md): Encrypted Direct Message
|
||||||
- [x] [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md): Mapping Nostr keys to DNS-based internet identifiers
|
- [x] [NIP-05](https://github.com/nostr-protocol/nips/blob/master/05.md): Mapping Nostr keys to DNS-based internet identifiers
|
||||||
- [ ] [NIP-06](https://github.com/nostr-protocol/nips/blob/master/06.md): Basic key derivation from mnemonic seed phrase
|
- [ ] [NIP-06](https://github.com/nostr-protocol/nips/blob/master/06.md): Basic key derivation from mnemonic seed phrase
|
||||||
- [ ] [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md): `window.nostr` capability for web browsers
|
- [x] [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md): `window.nostr` capability for web browsers
|
||||||
- [ ] [NIP-08](https://github.com/nostr-protocol/nips/blob/master/08.md): Handling Mentions
|
- [ ] [NIP-08](https://github.com/nostr-protocol/nips/blob/master/08.md): Handling Mentions
|
||||||
- [ ] [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md): Event Deletion
|
- [ ] [NIP-09](https://github.com/nostr-protocol/nips/blob/master/09.md): Event Deletion
|
||||||
- [ ] [NIP-11](https://github.com/nostr-protocol/nips/blob/master/11.md): Relay Information Document
|
- [ ] [NIP-11](https://github.com/nostr-protocol/nips/blob/master/11.md): Relay Information Document
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- add relay selection to global feed and allow user to specify custom relay
|
|
||||||
- add `client` tag to published events
|
- add `client` tag to published events
|
||||||
- add relay selection to global feed
|
- add relay selection to global feed
|
||||||
- add button for creating lightning invoice via WebLN
|
- add button for creating lightning invoice via WebLN
|
||||||
@@ -65,6 +64,7 @@
|
|||||||
- sort replies by date
|
- sort replies by date
|
||||||
- filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers)
|
- filter list of followers by users the user has blocked/reported (stops bots/spammers from showing up at followers)
|
||||||
- Add client side relay groups
|
- Add client side relay groups
|
||||||
|
- Add mentions in posts (https://css-tricks.com/so-you-want-to-build-an-mention-autocomplete-feature/)
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
11
src/app.tsx
11
src/app.tsx
@@ -19,6 +19,8 @@ import UserFollowersTab from "./views/user/followers";
|
|||||||
import UserRelaysTab from "./views/user/relays";
|
import UserRelaysTab from "./views/user/relays";
|
||||||
import UserFollowingTab from "./views/user/following";
|
import UserFollowingTab from "./views/user/following";
|
||||||
import NoteView from "./views/note";
|
import NoteView from "./views/note";
|
||||||
|
import { LoginStartView } from "./views/login/start";
|
||||||
|
import { LoginNpubView } from "./views/login/npub";
|
||||||
|
|
||||||
const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
||||||
let location = useLocation();
|
let location = useLocation();
|
||||||
@@ -38,7 +40,14 @@ const RootPage = () => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{ path: "login", element: <LoginView /> },
|
{
|
||||||
|
path: "login",
|
||||||
|
element: <LoginView />,
|
||||||
|
children: [
|
||||||
|
{ path: "", element: <LoginStartView /> },
|
||||||
|
{ path: "npub", element: <LoginNpubView /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <RootPage />,
|
element: <RootPage />,
|
||||||
|
@@ -151,3 +151,9 @@ export const VerificationFailed = createIcon({
|
|||||||
d: "M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z",
|
d: "M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z",
|
||||||
defaultProps,
|
defaultProps,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const SpyIcon = createIcon({
|
||||||
|
displayName: "SpyIcon",
|
||||||
|
d: "M17 13a4 4 0 1 1-4 4h-2a4 4 0 1 1-.535-2h3.07A3.998 3.998 0 0 1 17 13zM7 15a2 2 0 1 0 0 4 2 2 0 0 0 0-4zm10 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4zM16 3a4 4 0 0 1 4 4v3h2v2H2v-2h2V7a4 4 0 0 1 4-4h8zm0 2H8c-1.054 0-2 .95-2 2v3h12V7c0-1.054-.95-2-2-2z",
|
||||||
|
defaultProps,
|
||||||
|
});
|
||||||
|
@@ -19,12 +19,14 @@ import { ReplyIcon } from "../icons";
|
|||||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||||
import { buildReply } from "../../helpers/nostr-event";
|
import { buildReply } from "../../helpers/nostr-event";
|
||||||
import { UserDnsIdentityIcon } from "../user-dns-identity";
|
import { UserDnsIdentityIcon } from "../user-dns-identity";
|
||||||
|
import { useReadonlyMode } from "../../hooks/use-readonly-mode";
|
||||||
|
|
||||||
export type NoteProps = {
|
export type NoteProps = {
|
||||||
event: NostrEvent;
|
event: NostrEvent;
|
||||||
};
|
};
|
||||||
export const Note = React.memo(({ event }: NoteProps) => {
|
export const Note = React.memo(({ event }: NoteProps) => {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const readonly = useReadonlyMode();
|
||||||
const { openModal } = useContext(PostModalContext);
|
const { openModal } = useContext(PostModalContext);
|
||||||
|
|
||||||
const pubkey = useSubject(identity.pubkey);
|
const pubkey = useSubject(identity.pubkey);
|
||||||
@@ -53,7 +55,14 @@ export const Note = React.memo(({ event }: NoteProps) => {
|
|||||||
<NoteContents event={event} trusted={following.includes(event.pubkey)} />
|
<NoteContents event={event} trusted={following.includes(event.pubkey)} />
|
||||||
</CardBody>
|
</CardBody>
|
||||||
<CardFooter padding="2" display="flex" gap="2">
|
<CardFooter padding="2" display="flex" gap="2">
|
||||||
<IconButton icon={<ReplyIcon />} title="Reply" aria-label="Reply" onClick={reply} size="xs" />
|
<IconButton
|
||||||
|
icon={<ReplyIcon />}
|
||||||
|
title="Reply"
|
||||||
|
aria-label="Reply"
|
||||||
|
onClick={reply}
|
||||||
|
size="xs"
|
||||||
|
isDisabled={readonly}
|
||||||
|
/>
|
||||||
<Box flexGrow={1} />
|
<Box flexGrow={1} />
|
||||||
<UserTipButton pubkey={event.pubkey} size="xs" />
|
<UserTipButton pubkey={event.pubkey} size="xs" />
|
||||||
<NoteRelays event={event} size="xs" />
|
<NoteRelays event={event} size="xs" />
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Avatar, Button, Container, Flex, Heading, IconButton, LinkOverlay, VStack } from "@chakra-ui/react";
|
import { Avatar, Button, Container, Flex, Heading, IconButton, LinkOverlay, Text, VStack } from "@chakra-ui/react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { FeedIcon, LogoutIcon, ProfileIcon, SettingsIcon } from "./icons";
|
import { FeedIcon, LogoutIcon, ProfileIcon, SettingsIcon } from "./icons";
|
||||||
import { ErrorBoundary } from "./error-boundary";
|
import { ErrorBoundary } from "./error-boundary";
|
||||||
@@ -10,10 +10,12 @@ import identity from "../services/identity";
|
|||||||
import { FollowingList } from "./following-list";
|
import { FollowingList } from "./following-list";
|
||||||
import { ReloadPrompt } from "./reload-prompt";
|
import { ReloadPrompt } from "./reload-prompt";
|
||||||
import { PostModalProvider } from "../providers/post-modal-provider";
|
import { PostModalProvider } from "../providers/post-modal-provider";
|
||||||
|
import { useReadonlyMode } from "../hooks/use-readonly-mode";
|
||||||
|
|
||||||
export const Page = ({ children }: { children: React.ReactNode }) => {
|
export const Page = ({ children }: { children: React.ReactNode }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
const readonly = useReadonlyMode();
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
return (
|
return (
|
||||||
@@ -72,6 +74,11 @@ export const Page = ({ children }: { children: React.ReactNode }) => {
|
|||||||
<Button onClick={() => identity.logout()} leftIcon={<LogoutIcon />}>
|
<Button onClick={() => identity.logout()} leftIcon={<LogoutIcon />}>
|
||||||
Logout
|
Logout
|
||||||
</Button>
|
</Button>
|
||||||
|
{readonly && (
|
||||||
|
<Text color="yellow.500" textAlign="center">
|
||||||
|
Readonly Mode
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<ConnectedRelays />
|
<ConnectedRelays />
|
||||||
</VStack>
|
</VStack>
|
||||||
<Flex flexGrow={1} direction="column" overflow="hidden">
|
<Flex flexGrow={1} direction="column" overflow="hidden">
|
||||||
|
6
src/hooks/use-readonly-mode.ts
Normal file
6
src/hooks/use-readonly-mode.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import identity from "../services/identity";
|
||||||
|
import useSubject from "./use-subject";
|
||||||
|
|
||||||
|
export function useReadonlyMode() {
|
||||||
|
return useSubject(identity.readonly);
|
||||||
|
}
|
@@ -14,6 +14,7 @@ class IdentityService {
|
|||||||
loading = new BehaviorSubject(true);
|
loading = new BehaviorSubject(true);
|
||||||
setup = new BehaviorSubject(false);
|
setup = new BehaviorSubject(false);
|
||||||
pubkey = new BehaviorSubject("");
|
pubkey = new BehaviorSubject("");
|
||||||
|
readonly = new BehaviorSubject(false);
|
||||||
// TODO: remove this when there is a service to manage user relays
|
// TODO: remove this when there is a service to manage user relays
|
||||||
relays = new BehaviorSubject<PresetRelays>({});
|
relays = new BehaviorSubject<PresetRelays>({});
|
||||||
private useExtension: boolean = false;
|
private useExtension: boolean = false;
|
||||||
@@ -25,11 +26,13 @@ class IdentityService {
|
|||||||
if (savedIdentity) {
|
if (savedIdentity) {
|
||||||
this.setup.next(true);
|
this.setup.next(true);
|
||||||
this.pubkey.next(savedIdentity.pubkey);
|
this.pubkey.next(savedIdentity.pubkey);
|
||||||
|
this.readonly.next(false);
|
||||||
this.secKey = savedIdentity.secKey;
|
this.secKey = savedIdentity.secKey;
|
||||||
this.useExtension = savedIdentity.useExtension;
|
this.useExtension = savedIdentity.useExtension;
|
||||||
} else {
|
} else {
|
||||||
this.setup.next(false);
|
this.setup.next(false);
|
||||||
this.pubkey.next("");
|
this.pubkey.next("");
|
||||||
|
this.readonly.next(false);
|
||||||
this.secKey = undefined;
|
this.secKey = undefined;
|
||||||
this.useExtension = false;
|
this.useExtension = false;
|
||||||
}
|
}
|
||||||
@@ -38,6 +41,7 @@ class IdentityService {
|
|||||||
|
|
||||||
async loginWithExtension() {
|
async loginWithExtension() {
|
||||||
if (window.nostr) {
|
if (window.nostr) {
|
||||||
|
this.loading.next(true);
|
||||||
const pubkey = await window.nostr.getPublicKey();
|
const pubkey = await window.nostr.getPublicKey();
|
||||||
settings.identity.next({
|
settings.identity.next({
|
||||||
pubkey,
|
pubkey,
|
||||||
@@ -60,7 +64,14 @@ class IdentityService {
|
|||||||
// });
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout() {
|
loginWithPubkey(pubkey: string) {
|
||||||
|
this.readonly.next(true);
|
||||||
|
this.pubkey.next(pubkey);
|
||||||
|
this.setup.next(true);
|
||||||
|
this.loading.next(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
settings.identity.next(null);
|
settings.identity.next(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,10 @@ import settings from "../../services/settings";
|
|||||||
import { AddIcon } from "@chakra-ui/icons";
|
import { AddIcon } from "@chakra-ui/icons";
|
||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||||
|
import { useReadonlyMode } from "../../hooks/use-readonly-mode";
|
||||||
|
|
||||||
export const FollowingTab = () => {
|
export const FollowingTab = () => {
|
||||||
|
const readonly = useReadonlyMode();
|
||||||
const pubkey = useSubject(identity.pubkey);
|
const pubkey = useSubject(identity.pubkey);
|
||||||
const relays = useSubject(settings.relays);
|
const relays = useSubject(settings.relays);
|
||||||
const { openModal } = useContext(PostModalContext);
|
const { openModal } = useContext(PostModalContext);
|
||||||
@@ -35,7 +37,7 @@ export const FollowingTab = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex direction="column" gap="2">
|
<Flex direction="column" gap="2">
|
||||||
<Button variant="outline" leftIcon={<AddIcon />} onClick={() => openModal()}>
|
<Button variant="outline" leftIcon={<AddIcon />} onClick={() => openModal()} isDisabled={readonly}>
|
||||||
New Post
|
New Post
|
||||||
</Button>
|
</Button>
|
||||||
<FormControl display="flex" alignItems="center">
|
<FormControl display="flex" alignItems="center">
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
import { Avatar, Button, Flex, Heading, Spinner } from "@chakra-ui/react";
|
|
||||||
import { Navigate, useLocation } from "react-router-dom";
|
|
||||||
import useSubject from "../hooks/use-subject";
|
|
||||||
import identity from "../services/identity";
|
|
||||||
|
|
||||||
export const LoginView = () => {
|
|
||||||
const setup = useSubject(identity.setup);
|
|
||||||
const loading = useSubject(identity.loading);
|
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
if (loading) return <Spinner />;
|
|
||||||
if (setup) return <Navigate to={location.state?.from ?? "/"} replace />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex direction="column" alignItems="center" justifyContent="center" gap="4" height="80%">
|
|
||||||
<Avatar src="/apple-touch-icon.png" size="lg" />
|
|
||||||
<Heading>noStrudel</Heading>
|
|
||||||
<Button onClick={() => identity.loginWithExtension()}>Use browser extension</Button>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
19
src/views/login/index.tsx
Normal file
19
src/views/login/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Avatar, Box, Flex, Heading } from "@chakra-ui/react";
|
||||||
|
import { Navigate, Outlet, useLocation } from "react-router-dom";
|
||||||
|
import useSubject from "../../hooks/use-subject";
|
||||||
|
import identity from "../../services/identity";
|
||||||
|
|
||||||
|
export const LoginView = () => {
|
||||||
|
const setup = useSubject(identity.setup);
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
if (setup) return <Navigate to={location.state?.from ?? "/"} replace />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex direction="column" alignItems="center" justifyContent="center" gap="4" height="80%" px="4">
|
||||||
|
<Avatar src="/apple-touch-icon.png" size="lg" />
|
||||||
|
<Heading>noStrudel</Heading>
|
||||||
|
<Outlet />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
60
src/views/login/npub.tsx
Normal file
60
src/views/login/npub.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { Button, Flex, FormControl, FormHelperText, FormLabel, Input, Link, useToast } from "@chakra-ui/react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { RelayUrlInput } from "../../components/relay-url-input";
|
||||||
|
import { normalizeToHex } from "../../helpers/nip-19";
|
||||||
|
import identity from "../../services/identity";
|
||||||
|
import settings from "../../services/settings";
|
||||||
|
|
||||||
|
export const LoginNpubView = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const toast = useToast();
|
||||||
|
const [npub, setNpub] = useState("");
|
||||||
|
const [relay, setRelay] = useState("");
|
||||||
|
|
||||||
|
const handleSubmit: React.FormEventHandler<HTMLDivElement> = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const pubkey = normalizeToHex(npub);
|
||||||
|
if (!pubkey) {
|
||||||
|
return toast({ status: "error", title: "Invalid npub" });
|
||||||
|
}
|
||||||
|
|
||||||
|
identity.loginWithPubkey(pubkey);
|
||||||
|
// TODO: the settings service should not be in charge of the relays
|
||||||
|
if (!settings.relays.value.includes(relay)) {
|
||||||
|
settings.relays.next([...settings.relays.value, relay]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex as="form" direction="column" gap="4" onSubmit={handleSubmit}>
|
||||||
|
<Button variant="link" onClick={() => navigate("../")}>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Enter user npub</FormLabel>
|
||||||
|
<Input type="text" placeholder="npub1" isRequired value={npub} onChange={(e) => setNpub(e.target.value)} />
|
||||||
|
<FormHelperText>
|
||||||
|
Enter any npub you want.{" "}
|
||||||
|
<Link isExternal href="https://nostr.directory" color="blue.500" target="_blank">
|
||||||
|
nostr.directory
|
||||||
|
</Link>
|
||||||
|
</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl>
|
||||||
|
<FormLabel>Bootstrap relay</FormLabel>
|
||||||
|
<RelayUrlInput
|
||||||
|
placeholder="wss://nostr.example.com"
|
||||||
|
isRequired
|
||||||
|
value={relay}
|
||||||
|
onChange={(e) => setRelay(e.target.value)}
|
||||||
|
/>
|
||||||
|
<FormHelperText>The first relay to connect to.</FormHelperText>
|
||||||
|
</FormControl>
|
||||||
|
<Button colorScheme="brand" ml="auto" type="submit">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
21
src/views/login/start.tsx
Normal file
21
src/views/login/start.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { Button, Spinner } from "@chakra-ui/react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import useSubject from "../../hooks/use-subject";
|
||||||
|
import identity from "../../services/identity";
|
||||||
|
|
||||||
|
export const LoginStartView = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const loading = useSubject(identity.loading);
|
||||||
|
if (loading) return <Spinner />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => identity.loginWithExtension()} colorScheme="brand">
|
||||||
|
Use browser extension
|
||||||
|
</Button>
|
||||||
|
<Button variant="link" onClick={() => navigate("./npub")}>
|
||||||
|
Login with npub
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@@ -1,17 +1,36 @@
|
|||||||
import { Avatar, MenuItem } from "@chakra-ui/react";
|
import { Avatar, MenuItem } from "@chakra-ui/react";
|
||||||
import { MenuIconButton, MenuIconButtonProps } from "../../../components/menu-icon-button";
|
import { MenuIconButton, MenuIconButtonProps } from "../../../components/menu-icon-button";
|
||||||
|
|
||||||
import { ClipboardIcon, IMAGE_ICONS } from "../../../components/icons";
|
import { ClipboardIcon, IMAGE_ICONS, SpyIcon } from "../../../components/icons";
|
||||||
import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip-19";
|
import { Bech32Prefix, normalizeToBech32 } from "../../../helpers/nip-19";
|
||||||
import { useCopyToClipboard } from "react-use";
|
import { useCopyToClipboard } from "react-use";
|
||||||
import { truncatedId } from "../../../helpers/nostr-event";
|
import { truncatedId } from "../../../helpers/nostr-event";
|
||||||
|
import identity from "../../../services/identity";
|
||||||
|
import { useUserMetadata } from "../../../hooks/use-user-metadata";
|
||||||
|
import { getUserDisplayName } from "../../../helpers/user-metadata";
|
||||||
|
|
||||||
export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<MenuIconButtonProps, "children">) => {
|
export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<MenuIconButtonProps, "children">) => {
|
||||||
const [_clipboardState, copyToClipboard] = useCopyToClipboard();
|
const [_clipboardState, copyToClipboard] = useCopyToClipboard();
|
||||||
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
||||||
|
const metadata = useUserMetadata(pubkey);
|
||||||
|
|
||||||
|
const loginAsUser = () => {
|
||||||
|
if (confirm(`Do you want to logout and login as ${getUserDisplayName(metadata, pubkey)}?`)) {
|
||||||
|
identity.logout();
|
||||||
|
identity.loginWithPubkey(pubkey);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuIconButton {...props}>
|
<MenuIconButton {...props}>
|
||||||
|
<MenuItem icon={<SpyIcon fontSize="1.5em" />} onClick={() => loginAsUser()}>
|
||||||
|
Login as {getUserDisplayName(metadata, pubkey)}
|
||||||
|
</MenuItem>
|
||||||
|
{npub && (
|
||||||
|
<MenuItem onClick={() => copyToClipboard(npub)} icon={<ClipboardIcon fontSize="1.5em" />}>
|
||||||
|
Copy {truncatedId(npub)}
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
as="a"
|
as="a"
|
||||||
icon={<Avatar src={IMAGE_ICONS.nostrGuruIcon} size="xs" />}
|
icon={<Avatar src={IMAGE_ICONS.nostrGuruIcon} size="xs" />}
|
||||||
@@ -44,11 +63,6 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
|||||||
>
|
>
|
||||||
Open in snort.social
|
Open in snort.social
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{npub && (
|
|
||||||
<MenuItem onClick={() => copyToClipboard(npub)} icon={<ClipboardIcon />}>
|
|
||||||
Copy {truncatedId(npub)}
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
</MenuIconButton>
|
</MenuIconButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user