Dont require login for profile and note views

This commit is contained in:
hzrd149
2023-06-05 12:22:54 -04:00
parent 780491aa6c
commit 2aa6ec5678
30 changed files with 249 additions and 126 deletions

View File

@ -0,0 +1,5 @@
---
"nostrudel": minor
---
Dont require login for profile and note views

View File

@ -37,37 +37,12 @@ import UserMediaTab from "./views/user/media";
// code split search view because QrScanner library is 400kB // code split search view because QrScanner library is 400kB
const SearchView = React.lazy(() => import("./views/search")); const SearchView = React.lazy(() => import("./views/search"));
const RequireCurrentAccount = ({ children }: { children: JSX.Element }) => {
let location = useLocation();
const loading = useSubject(accountService.loading);
const account = useSubject(accountService.current);
if (loading) {
return (
<Flex alignItems="center" height="100%" gap="4" direction="column">
<Flex gap="4" grow="1" alignItems="center">
<Spinner />
<Text>Loading Accounts</Text>
</Flex>
<Button variant="link" margin="4" onClick={() => deleteDatabase()}>
Stuck loading? clear cache
</Button>
</Flex>
);
}
if (!account) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
return children;
};
const RootPage = () => ( const RootPage = () => (
<RequireCurrentAccount> <Page>
<Page> <Suspense fallback={<Spinner />}>
<Suspense fallback={<Spinner />}> <Outlet />
<Outlet /> </Suspense>
</Suspense> </Page>
</Page>
</RequireCurrentAccount>
); );
const router = createBrowserRouter([ const router = createBrowserRouter([

View File

@ -18,7 +18,7 @@ export function QuoteRepostButton({ event }: { event: NostrEvent }) {
onClick={handleClick} onClick={handleClick}
aria-label="Quote repost" aria-label="Quote repost"
title="Quote repost" title="Quote repost"
isDisabled={account.readonly} isDisabled={account?.readonly ?? true}
/> />
); );
} }

View File

@ -45,7 +45,7 @@ export default function ReactionButton({ note, ...props }: { note: NostrEvent }
handleClick(input); handleClick(input);
}; };
const isLiked = reactions.some((event) => event.pubkey === account.pubkey); const isLiked = !!account && reactions.some((event) => event.pubkey === account.pubkey);
return ( return (
// <Popover placement="bottom" trigger="hover" openDelay={500}> // <Popover placement="bottom" trigger="hover" openDelay={500}>

View File

@ -13,6 +13,12 @@ export function ReplyButton({ event }: { event: NostrEvent }) {
const reply = () => openModal(buildReply(event)); const reply = () => openModal(buildReply(event));
return ( return (
<IconButton icon={<ReplyIcon />} title="Reply" aria-label="Reply" onClick={reply} isDisabled={account.readonly} /> <IconButton
icon={<ReplyIcon />}
title="Reply"
aria-label="Reply"
onClick={reply}
isDisabled={account?.readonly ?? true}
/>
); );
} }

View File

@ -30,6 +30,7 @@ export function RepostButton({ event }: { event: NostrEvent }) {
const handleClick = async () => { const handleClick = async () => {
try { try {
if (!account) throw new Error("not logged in");
setLoading(true); setLoading(true);
const draftRepost = buildRepost(event); const draftRepost = buildRepost(event);
const repost = await signingService.requestSignature(draftRepost, account); const repost = await signingService.requestSignature(draftRepost, account);
@ -50,7 +51,7 @@ export function RepostButton({ event }: { event: NostrEvent }) {
onClick={onOpen} onClick={onOpen}
aria-label="Repost Note" aria-label="Repost Note"
title="Repost Note" title="Repost Note"
isDisabled={account.readonly} isDisabled={account?.readonly ?? true}
isLoading={loading} isLoading={loading}
/> />
{isOpen && ( {isOpen && (

View File

@ -43,6 +43,7 @@ export const NoteMenu = ({ event, ...props }: { event: NostrEvent } & Omit<MenuI
const deleteNote = useCallback(async () => { const deleteNote = useCallback(async () => {
try { try {
if (!account) throw new Error("not logged in");
setDeleting(true); setDeleting(true);
const deleteEvent = buildDeleteEvent([event.id], reason); const deleteEvent = buildDeleteEvent([event.id], reason);
const signed = await signingService.requestSignature(deleteEvent, account); const signed = await signingService.requestSignature(deleteEvent, account);
@ -75,7 +76,7 @@ export const NoteMenu = ({ event, ...props }: { event: NostrEvent } & Omit<MenuI
Copy Note ID Copy Note ID
</MenuItem> </MenuItem>
)} )}
{account.pubkey === event.pubkey && ( {account?.pubkey === event.pubkey && (
<MenuItem icon={<TrashIcon />} color="red.500" onClick={deleteModal.onOpen}> <MenuItem icon={<TrashIcon />} color="red.500" onClick={deleteModal.onOpen}>
Delete Note Delete Note
</MenuItem> </MenuItem>

View File

@ -26,7 +26,7 @@ export default function NoteZapButton({ note, ...props }: { note: NostrEvent } &
}, [zaps]); }, [zaps]);
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const hasZapped = parsedZaps.some((zapRequest) => zapRequest.request.pubkey === account.pubkey); const hasZapped = !!account && parsedZaps.some((zapRequest) => zapRequest.request.pubkey === account.pubkey);
const tipAddress = metadata?.lud06 || metadata?.lud16; const tipAddress = metadata?.lud06 || metadata?.lud16;
const invoicePaid = () => eventZapsService.requestZaps(note.id, clientRelaysService.getReadUrls(), true); const invoicePaid = () => eventZapsService.requestZaps(note.id, clientRelaysService.getReadUrls(), true);

View File

@ -1,8 +1,8 @@
import React, { PropsWithChildren, useContext } from "react"; import React, { PropsWithChildren, useContext } from "react";
import { NostrEvent } from "../../types/nostr-event"; import { NostrEvent } from "../../types/nostr-event";
import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { useUserContacts } from "../../hooks/use-user-contacts";
import { useCurrentAccount } from "../../hooks/use-current-account"; import { useCurrentAccount } from "../../hooks/use-current-account";
import clientFollowingService from "../../services/client-following";
import useSubject from "../../hooks/use-subject";
const TrustContext = React.createContext<boolean>(false); const TrustContext = React.createContext<boolean>(false);
@ -18,11 +18,9 @@ export function TrustProvider({
const parentTrust = useContext(TrustContext); const parentTrust = useContext(TrustContext);
const account = useCurrentAccount(); const account = useCurrentAccount();
const readRelays = useReadRelayUrls(); const following = useSubject(clientFollowingService.following).map((p) => p[1]);
const contacts = useUserContacts(account.pubkey, readRelays);
const following = contacts?.contacts || [];
const isEventTrusted = trust || (!!event && (event.pubkey === account.pubkey || following.includes(event.pubkey))); const isEventTrusted = trust || (!!event && (event.pubkey === account?.pubkey || following.includes(event.pubkey)));
return <TrustContext.Provider value={parentTrust || isEventTrusted}>{children}</TrustContext.Provider>; return <TrustContext.Provider value={parentTrust || isEventTrusted}>{children}</TrustContext.Provider>;
} }

View File

@ -5,7 +5,7 @@ import { useCurrentAccount } from "../../hooks/use-current-account";
import accountService from "../../services/account"; import accountService from "../../services/account";
import { ConnectedRelays } from "../connected-relays"; import { ConnectedRelays } from "../connected-relays";
import { ChatIcon, FeedIcon, LogoutIcon, NotificationIcon, ProfileIcon, RelayIcon, SearchIcon } from "../icons"; import { ChatIcon, FeedIcon, LogoutIcon, NotificationIcon, ProfileIcon, RelayIcon, SearchIcon } from "../icons";
import { ProfileButton } from "../profile-button"; import ProfileLink from "./profile-link";
import AccountSwitcher from "./account-switcher"; import AccountSwitcher from "./account-switcher";
export default function DesktopSideNav() { export default function DesktopSideNav() {
@ -19,7 +19,7 @@ export default function DesktopSideNav() {
<Avatar src="/apple-touch-icon.png" size="sm" /> <Avatar src="/apple-touch-icon.png" size="sm" />
<Heading size="md">noStrudel</Heading> <Heading size="md">noStrudel</Heading>
</Flex> </Flex>
<ProfileButton /> <ProfileLink />
<AccountSwitcher /> <AccountSwitcher />
<Button onClick={() => navigate("/")} leftIcon={<FeedIcon />}> <Button onClick={() => navigate("/")} leftIcon={<FeedIcon />}>
Home Home
@ -42,10 +42,12 @@ export default function DesktopSideNav() {
<Button onClick={() => navigate("/settings")} leftIcon={<SettingsIcon />}> <Button onClick={() => navigate("/settings")} leftIcon={<SettingsIcon />}>
Settings Settings
</Button> </Button>
<Button onClick={() => accountService.logout()} leftIcon={<LogoutIcon />}> {account && (
Logout <Button onClick={() => accountService.logout()} leftIcon={<LogoutIcon />}>
</Button> Logout
{account.readonly && ( </Button>
)}
{account?.readonly && (
<Text color="red.200" textAlign="center"> <Text color="red.200" textAlign="center">
Readonly Mode Readonly Mode
</Text> </Text>

View File

@ -1,9 +1,9 @@
import { Flex, IconButton, useDisclosure } from "@chakra-ui/react"; import { Avatar, Flex, IconButton, useDisclosure } from "@chakra-ui/react";
import { useContext, useEffect } from "react"; import { useContext, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { useCurrentAccount } from "../../hooks/use-current-account"; import { useCurrentAccount } from "../../hooks/use-current-account";
import { PostModalContext } from "../../providers/post-modal-provider"; import { PostModalContext } from "../../providers/post-modal-provider";
import { ChatIcon, HomeIcon, NotificationIcon, PlusCircleIcon, SearchIcon } from "../icons"; import { ChatIcon, FeedIcon, HomeIcon, NotificationIcon, PlusCircleIcon, SearchIcon } from "../icons";
import { UserAvatar } from "../user-avatar"; import { UserAvatar } from "../user-avatar";
import MobileSideDrawer from "./mobile-side-drawer"; import MobileSideDrawer from "./mobile-side-drawer";
@ -19,7 +19,11 @@ export default function MobileBottomNav() {
return ( return (
<> <>
<Flex flexShrink={0} gap="2" padding="2" alignItems="center"> <Flex flexShrink={0} gap="2" padding="2" alignItems="center">
<UserAvatar pubkey={account.pubkey} size="sm" onClick={onOpen} /> {account ? (
<UserAvatar pubkey={account.pubkey} size="sm" onClick={onOpen} noProxy />
) : (
<Avatar size="sm" src="/apple-touch-icon.png" onClick={onOpen} cursor="pointer" />
)}
<IconButton icon={<HomeIcon />} aria-label="Home" onClick={() => navigate("/")} flexGrow="1" size="md" /> <IconButton icon={<HomeIcon />} aria-label="Home" onClick={() => navigate("/")} flexGrow="1" size="md" />
<IconButton <IconButton
icon={<SearchIcon />} icon={<SearchIcon />}
@ -36,7 +40,7 @@ export default function MobileBottomNav() {
}} }}
variant="solid" variant="solid"
colorScheme="brand" colorScheme="brand"
isDisabled={account.readonly} isDisabled={account?.readonly ?? true}
/> />
<IconButton icon={<ChatIcon />} aria-label="Messages" onClick={() => navigate(`/dm`)} flexGrow="1" size="md" /> <IconButton icon={<ChatIcon />} aria-label="Messages" onClick={() => navigate(`/dm`)} flexGrow="1" size="md" />
<IconButton <IconButton

View File

@ -1,4 +1,5 @@
import { import {
Avatar,
Button, Button,
Drawer, Drawer,
DrawerBody, DrawerBody,
@ -10,32 +11,36 @@ import {
Flex, Flex,
Text, Text,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { useNavigate } from "react-router-dom"; import { Link as RouterLink, useNavigate } from "react-router-dom";
import { getUserDisplayName } from "../../helpers/user-metadata";
import { useCurrentAccount } from "../../hooks/use-current-account";
import { useUserMetadata } from "../../hooks/use-user-metadata";
import accountService from "../../services/account";
import { ConnectedRelays } from "../connected-relays"; import { ConnectedRelays } from "../connected-relays";
import { HomeIcon, LogoutIcon, ProfileIcon, RelayIcon, SearchIcon, SettingsIcon } from "../icons"; import { HomeIcon, LogoutIcon, ProfileIcon, RelayIcon, SearchIcon, SettingsIcon } from "../icons";
import { UserAvatar } from "../user-avatar"; import { UserAvatar } from "../user-avatar";
import { UserLink } from "../user-link"; import { UserLink } from "../user-link";
import AccountSwitcher from "./account-switcher"; import AccountSwitcher from "./account-switcher";
import { useCurrentAccount } from "../../hooks/use-current-account";
import accountService from "../../services/account";
export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "children">) { export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "children">) {
const navigate = useNavigate(); const navigate = useNavigate();
const account = useCurrentAccount(); const account = useCurrentAccount();
const metadata = useUserMetadata(account.pubkey);
return ( return (
<Drawer placement="left" {...props}> <Drawer placement="left" {...props}>
<DrawerOverlay /> <DrawerOverlay />
<DrawerContent> <DrawerContent>
<DrawerCloseButton /> <DrawerCloseButton />
<DrawerHeader> <DrawerHeader px="4" py="4">
<Flex gap="2"> {account ? (
<UserAvatar pubkey={account.pubkey} size="sm" /> <Flex gap="2">
<UserLink pubkey={account.pubkey} /> <UserAvatar pubkey={account.pubkey} size="sm" noProxy />
</Flex> <UserLink pubkey={account.pubkey} />
</Flex>
) : (
<Flex gap="2">
<Avatar src="/apple-touch-icon.png" size="sm" />
<Text m={0}>Nostrudel</Text>
</Flex>
)}
</DrawerHeader> </DrawerHeader>
<DrawerBody padding={0} overflowY="auto" overflowX="hidden"> <DrawerBody padding={0} overflowY="auto" overflowX="hidden">
<AccountSwitcher /> <AccountSwitcher />
@ -55,9 +60,15 @@ export default function MobileSideDrawer({ ...props }: Omit<DrawerProps, "childr
<Button onClick={() => navigate("/settings")} leftIcon={<SettingsIcon />}> <Button onClick={() => navigate("/settings")} leftIcon={<SettingsIcon />}>
Settings Settings
</Button> </Button>
<Button onClick={() => accountService.logout()} leftIcon={<LogoutIcon />}> {account ? (
Logout <Button onClick={() => accountService.logout()} leftIcon={<LogoutIcon />}>
</Button> Logout
</Button>
) : (
<Button as={RouterLink} to="/login" colorScheme="brand">
Login
</Button>
)}
<ConnectedRelays /> <ConnectedRelays />
</Flex> </Flex>
</DrawerBody> </DrawerBody>

View File

@ -0,0 +1,40 @@
import { Box, Button, LinkBox, Text } from "@chakra-ui/react";
import { Link as RouterLink } from "react-router-dom";
import { UserAvatar } from "../user-avatar";
import { useUserMetadata } from "../../hooks/use-user-metadata";
import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip19";
import { truncatedId } from "../../helpers/nostr-event";
import { useCurrentAccount } from "../../hooks/use-current-account";
function ProfileButton() {
const account = useCurrentAccount()!;
const metadata = useUserMetadata(account.pubkey);
return (
<LinkBox
as={RouterLink}
to={`/u/${normalizeToBech32(account.pubkey, Bech32Prefix.Pubkey)}`}
display="flex"
gap="2"
overflow="hidden"
>
<UserAvatar pubkey={account.pubkey} noProxy />
<Box>
<Text fontWeight="bold">{metadata?.name}</Text>
<Text>{truncatedId(normalizeToBech32(account.pubkey) ?? "")}</Text>
</Box>
</LinkBox>
);
}
export default function ProfileLink() {
const account = useCurrentAccount();
if (account) return <ProfileButton />;
else
return (
<Button as={RouterLink} to="/login" state={{ from: location.pathname }} colorScheme="brand">
Login
</Button>
);
}

View File

@ -1,28 +0,0 @@
import { Box, LinkBox, Text } from "@chakra-ui/react";
import { Link } from "react-router-dom";
import { UserAvatar } from "./user-avatar";
import { useUserMetadata } from "../hooks/use-user-metadata";
import { Bech32Prefix, normalizeToBech32 } from "../helpers/nip19";
import { truncatedId } from "../helpers/nostr-event";
import { useCurrentAccount } from "../hooks/use-current-account";
export const ProfileButton = () => {
const { pubkey } = useCurrentAccount();
const metadata = useUserMetadata(pubkey);
return (
<LinkBox
as={Link}
to={`/u/${normalizeToBech32(pubkey, Bech32Prefix.Pubkey)}`}
display="flex"
gap="2"
overflow="hidden"
>
<UserAvatar pubkey={pubkey} noProxy />
<Box>
<Text fontWeight="bold">{metadata?.name}</Text>
<Text>{truncatedId(normalizeToBech32(pubkey) ?? "")}</Text>
</Box>
</LinkBox>
);
};

View File

@ -2,8 +2,6 @@ import { Button, ButtonProps } from "@chakra-ui/react";
import { useCurrentAccount } from "../hooks/use-current-account"; import { useCurrentAccount } from "../hooks/use-current-account";
import useSubject from "../hooks/use-subject"; import useSubject from "../hooks/use-subject";
import clientFollowingService from "../services/client-following"; import clientFollowingService from "../services/client-following";
import { useAsync } from "react-use";
import { NostrRequest } from "../classes/nostr-request";
import clientRelaysService from "../services/client-relays"; import clientRelaysService from "../services/client-relays";
import { useUserContacts } from "../hooks/use-user-contacts"; import { useUserContacts } from "../hooks/use-user-contacts";
@ -30,8 +28,14 @@ export const UserFollowButton = ({
}; };
return ( return (
<Button colorScheme="brand" {...props} isLoading={savingDraft} onClick={toggleFollow} isDisabled={account.readonly}> <Button
{isFollowing ? "Unfollow" : userContacts?.contacts.includes(account.pubkey) ? "Follow Back" : "Follow"} colorScheme="brand"
{...props}
isLoading={savingDraft}
onClick={toggleFollow}
isDisabled={account?.readonly ?? true}
>
{isFollowing ? "Unfollow" : account && userContacts?.contacts.includes(account.pubkey) ? "Follow Back" : "Follow"}
</Button> </Button>
); );
}; };

View File

@ -2,7 +2,5 @@ import accountService from "../services/account";
import useSubject from "./use-subject"; import useSubject from "./use-subject";
export function useCurrentAccount() { export function useCurrentAccount() {
const account = useSubject(accountService.current); return useSubject(accountService.current);
if (!account) throw Error("no account");
return account;
} }

View File

@ -0,0 +1,44 @@
import { Link, useLocation } from "react-router-dom";
import useSubject from "../hooks/use-subject";
import accountService from "../services/account";
import { Button, Flex, Heading, Spinner, Text } from "@chakra-ui/react";
import { deleteDatabase } from "../services/db";
import { ExternalLinkIcon } from "../components/icons";
export default function RequireCurrentAccount({ children }: { children: JSX.Element }) {
let location = useLocation();
const loading = useSubject(accountService.loading);
const account = useSubject(accountService.current);
if (loading) {
return (
<Flex alignItems="center" height="100%" gap="4" direction="column">
<Flex gap="4" grow="1" alignItems="center">
<Spinner />
<Text>Loading Accounts</Text>
</Flex>
<Button variant="link" margin="4" onClick={() => deleteDatabase()}>
Stuck loading? clear cache
</Button>
</Flex>
);
}
if (!account)
return (
<Flex direction="column" w="full" h="full" alignItems="center" justifyContent="center" gap="4">
<Heading size="md">You must be logged in to use this view</Heading>
<Button
as={Link}
to="/login"
state={{ from: location.pathname }}
colorScheme="brand"
rightIcon={<ExternalLinkIcon />}
>
Login
</Button>
</Flex>
);
return children;
}

View File

@ -4,7 +4,7 @@ import { AppSettings } from "./user-app-settings";
export type Account = { export type Account = {
pubkey: string; pubkey: string;
readonly?: boolean; readonly: boolean;
relays?: string[]; relays?: string[];
secKey?: ArrayBuffer; secKey?: ArrayBuffer;
iv?: Uint8Array; iv?: Uint8Array;

View File

@ -10,6 +10,14 @@ import signingService from "./signing";
export type RelayDirectory = Record<string, { read: boolean; write: boolean }>; export type RelayDirectory = Record<string, { read: boolean; write: boolean }>;
const DEFAULT_RELAYS = [
{ url: "wss://relay.damus.io", mode: RelayMode.READ },
{ url: "wss://nostr.wine", mode: RelayMode.READ },
{ url: "wss://relay.snort.social", mode: RelayMode.READ },
{ url: "wss://eden.nostr.land", mode: RelayMode.READ },
{ url: "wss://nos.lol", mode: RelayMode.READ },
];
class ClientRelayService { class ClientRelayService {
bootstrapRelays = new Set<string>(); bootstrapRelays = new Set<string>();
relays = new PersistentSubject<RelayConfig[]>([]); relays = new PersistentSubject<RelayConfig[]>([]);
@ -19,9 +27,10 @@ class ClientRelayService {
constructor() { constructor() {
let lastSubject: Subject<ParsedUserRelays> | undefined; let lastSubject: Subject<ParsedUserRelays> | undefined;
accountService.current.subscribe((account) => { accountService.current.subscribe((account) => {
this.relays.next([]); if (!account) {
this.relays.next(DEFAULT_RELAYS);
if (!account) return; return;
} else this.relays.next([]);
if (account.relays) { if (account.relays) {
this.bootstrapRelays.clear(); this.bootstrapRelays.clear();

View File

@ -19,6 +19,7 @@ import { DraftNostrEvent, NostrEvent } from "../../types/nostr-event";
import DecryptPlaceholder from "./decrypt-placeholder"; import DecryptPlaceholder from "./decrypt-placeholder";
import { EmbedableContent } from "../../helpers/embeds"; import { EmbedableContent } from "../../helpers/embeds";
import { embedImages, embedLinks, embedNostrLinks, embedVideos } from "../../components/embed-types"; import { embedImages, embedLinks, embedNostrLinks, embedVideos } from "../../components/embed-types";
import RequireCurrentAccount from "../../providers/require-current-account";
function MessageContent({ event, text }: { event: NostrEvent; text: string }) { function MessageContent({ event, text }: { event: NostrEvent; text: string }) {
let content: EmbedableContent = [text]; let content: EmbedableContent = [text];
@ -33,7 +34,7 @@ function MessageContent({ event, text }: { event: NostrEvent; text: string }) {
} }
function Message({ event }: { event: NostrEvent } & Omit<CardProps, "children">) { function Message({ event }: { event: NostrEvent } & Omit<CardProps, "children">) {
const account = useCurrentAccount(); const account = useCurrentAccount()!;
const isOwnMessage = account.pubkey === event.pubkey; const isOwnMessage = account.pubkey === event.pubkey;
return ( return (
@ -55,7 +56,7 @@ function Message({ event }: { event: NostrEvent } & Omit<CardProps, "children">)
); );
} }
export default function DirectMessageChatView() { function DirectMessageChatPage() {
const { key } = useParams(); const { key } = useParams();
if (!key) return <Navigate to="/" />; if (!key) return <Navigate to="/" />;
const pubkey = normalizeToHex(key); const pubkey = normalizeToHex(key);
@ -131,3 +132,10 @@ export default function DirectMessageChatView() {
</Flex> </Flex>
); );
} }
export default function DirectMessageChatView() {
return (
<RequireCurrentAccount>
<DirectMessageChatPage />
</RequireCurrentAccount>
);
}

View File

@ -25,6 +25,7 @@ import { useUserMetadata } from "../../hooks/use-user-metadata";
import directMessagesService from "../../services/direct-messages"; import directMessagesService from "../../services/direct-messages";
import { ExternalLinkIcon } from "../../components/icons"; import { ExternalLinkIcon } from "../../components/icons";
import { useIsMobile } from "../../hooks/use-is-mobile"; import { useIsMobile } from "../../hooks/use-is-mobile";
import RequireCurrentAccount from "../../providers/require-current-account";
function ContactCard({ pubkey }: { pubkey: string }) { function ContactCard({ pubkey }: { pubkey: string }) {
const subject = useMemo(() => directMessagesService.getUserMessages(pubkey), [pubkey]); const subject = useMemo(() => directMessagesService.getUserMessages(pubkey), [pubkey]);
@ -48,7 +49,7 @@ function ContactCard({ pubkey }: { pubkey: string }) {
); );
} }
function DirectMessagesView() { function DirectMessagesPage() {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const [from, setFrom] = useState(moment().subtract(2, "days")); const [from, setFrom] = useState(moment().subtract(2, "days"));
const conversations = useSubject(directMessagesService.conversations); const conversations = useSubject(directMessagesService.conversations);
@ -121,4 +122,10 @@ function DirectMessagesView() {
); );
} }
export default DirectMessagesView; export default function DirectMessagesView() {
return (
<RequireCurrentAccount>
<DirectMessagesPage />
</RequireCurrentAccount>
);
}

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo } from "react"; import { useMemo } from "react";
import { Button, Flex, Spinner } from "@chakra-ui/react"; import { Button, Flex, Spinner } from "@chakra-ui/react";
import moment from "moment"; import moment from "moment";
import { Note } from "../../components/note"; import { Note } from "../../components/note";
@ -11,6 +11,7 @@ import userContactsService, { UserContacts } from "../../services/user-contacts"
import { PersistentSubject } from "../../classes/subject"; import { PersistentSubject } from "../../classes/subject";
import useSubject from "../../hooks/use-subject"; import useSubject from "../../hooks/use-subject";
import { useThrottle } from "react-use"; import { useThrottle } from "react-use";
import RequireCurrentAccount from "../../providers/require-current-account";
class DiscoverContacts { class DiscoverContacts {
pubkey: string; pubkey: string;
@ -59,9 +60,9 @@ class DiscoverContacts {
} }
} }
export default function DiscoverTab() { function DiscoverTabBody() {
useAppTitle("discover"); useAppTitle("discover");
const account = useCurrentAccount(); const account = useCurrentAccount()!;
const relays = useReadRelayUrls(); const relays = useReadRelayUrls();
const discover = useMemo(() => new DiscoverContacts(account.pubkey, relays), [account.pubkey, relays.join("|")]); const discover = useMemo(() => new DiscoverContacts(account.pubkey, relays), [account.pubkey, relays.join("|")]);
@ -86,3 +87,11 @@ export default function DiscoverTab() {
</Flex> </Flex>
); );
} }
export default function DiscoverTab() {
return (
<RequireCurrentAccount>
<DiscoverTabBody />
</RequireCurrentAccount>
);
}

View File

@ -11,9 +11,10 @@ import { PostModalContext } from "../../providers/post-modal-provider";
import { useReadRelayUrls } from "../../hooks/use-client-relays"; import { useReadRelayUrls } from "../../hooks/use-client-relays";
import { useCurrentAccount } from "../../hooks/use-current-account"; import { useCurrentAccount } from "../../hooks/use-current-account";
import RepostNote from "../../components/note/repost-note"; import RepostNote from "../../components/note/repost-note";
import RequireCurrentAccount from "../../providers/require-current-account";
export default function FollowingTab() { function FollowingTabBody() {
const account = useCurrentAccount(); const account = useCurrentAccount()!;
const readRelays = useReadRelayUrls(); const readRelays = useReadRelayUrls();
const { openModal } = useContext(PostModalContext); const { openModal } = useContext(PostModalContext);
const contacts = useUserContacts(account.pubkey, readRelays); const contacts = useUserContacts(account.pubkey, readRelays);
@ -55,3 +56,11 @@ export default function FollowingTab() {
</Flex> </Flex>
); );
} }
export default function FollowingTab() {
return (
<RequireCurrentAccount>
<FollowingTabBody />
</RequireCurrentAccount>
);
}

View File

@ -72,7 +72,7 @@ export default function LoginNsecView() {
const pubkey = getPublicKey(hexKey); const pubkey = getPublicKey(hexKey);
const encrypted = await signingService.encryptSecKey(hexKey); const encrypted = await signingService.encryptSecKey(hexKey);
accountService.addAccount({ pubkey, relays: [relayUrl], ...encrypted }); accountService.addAccount({ pubkey, relays: [relayUrl], ...encrypted, readonly: false });
clientRelaysService.bootstrapRelays.add(relayUrl); clientRelaysService.bootstrapRelays.add(relayUrl);
accountService.switchAccount(pubkey); accountService.switchAccount(pubkey);
}; };

View File

@ -42,7 +42,7 @@ export default function LoginStartView() {
relays = ["wss://relay.damus.io", "wss://relay.snort.social", "wss://nostr.wine"]; relays = ["wss://relay.damus.io", "wss://relay.snort.social", "wss://nostr.wine"];
} }
accountService.addAccount({ pubkey, relays, useExtension: true }); accountService.addAccount({ pubkey, relays, useExtension: true, readonly: false });
} }
accountService.switchAccount(pubkey); accountService.switchAccount(pubkey);

View File

@ -1,7 +1,6 @@
import { Button, Card, CardBody, CardHeader, Flex, Spinner, Text } from "@chakra-ui/react"; import { Button, Card, CardBody, CardHeader, Flex, Spinner, Text } from "@chakra-ui/react";
import moment from "moment"; import moment from "moment";
import { memo } from "react"; import { memo } from "react";
import { useNavigate } from "react-router-dom";
import { UserAvatar } from "../../components/user-avatar"; import { UserAvatar } from "../../components/user-avatar";
import { UserLink } from "../../components/user-link"; import { UserLink } from "../../components/user-link";
import { convertTimestampToDate } from "../../helpers/date"; import { convertTimestampToDate } from "../../helpers/date";
@ -10,6 +9,7 @@ import { useCurrentAccount } from "../../hooks/use-current-account";
import { useTimelineLoader } from "../../hooks/use-timeline-loader"; import { useTimelineLoader } from "../../hooks/use-timeline-loader";
import { NostrEvent } from "../../types/nostr-event"; import { NostrEvent } from "../../types/nostr-event";
import { NoteLink } from "../../components/note-link"; import { NoteLink } from "../../components/note-link";
import RequireCurrentAccount from "../../providers/require-current-account";
const Kind1Notification = ({ event }: { event: NostrEvent }) => ( const Kind1Notification = ({ event }: { event: NostrEvent }) => (
<Card size="sm" variant="outline"> <Card size="sm" variant="outline">
@ -35,9 +35,9 @@ const NotificationItem = memo(({ event }: { event: NostrEvent }) => {
return <>Unknown event type {event.kind}</>; return <>Unknown event type {event.kind}</>;
}); });
const NotificationsView = () => { function NotificationsPage() {
const readRelays = useReadRelayUrls(); const readRelays = useReadRelayUrls();
const account = useCurrentAccount(); const account = useCurrentAccount()!;
const { events, loading, loadMore } = useTimelineLoader( const { events, loading, loadMore } = useTimelineLoader(
"notifications", "notifications",
readRelays, readRelays,
@ -60,6 +60,12 @@ const NotificationsView = () => {
{loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>} {loading ? <Spinner ml="auto" mr="auto" mt="8" mb="8" /> : <Button onClick={() => loadMore()}>Load More</Button>}
</Flex> </Flex>
); );
}; }
export default NotificationsView; export default function NotificationsView() {
return (
<RequireCurrentAccount>
<NotificationsPage />
</RequireCurrentAccount>
);
}

View File

@ -49,7 +49,7 @@ type MetadataFormProps = {
}; };
const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => { const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
const account = useCurrentAccount(); const account = useCurrentAccount()!;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const { const {
register, register,
@ -189,7 +189,7 @@ export const ProfileEditView = () => {
const writeRelays = useWriteRelayUrls(); const writeRelays = useWriteRelayUrls();
const readRelays = useReadRelayUrls(); const readRelays = useReadRelayUrls();
const toast = useToast(); const toast = useToast();
const account = useCurrentAccount(); const account = useCurrentAccount()!;
const metadata = useUserMetadata(account.pubkey, readRelays, true); const metadata = useUserMetadata(account.pubkey, readRelays, true);
const defaultValues = useMemo<FormData>( const defaultValues = useMemo<FormData>(

View File

@ -1,5 +1,10 @@
import RequireCurrentAccount from "../../providers/require-current-account";
import { ProfileEditView } from "./edit"; import { ProfileEditView } from "./edit";
export default function ProfileView() { export default function ProfileView() {
return <ProfileEditView />; return (
<RequireCurrentAccount>
<ProfileEditView />
</RequireCurrentAccount>
);
} }

View File

@ -26,8 +26,9 @@ import useSubject from "../../hooks/use-subject";
import { RelayStatus } from "../../components/relay-status"; import { RelayStatus } from "../../components/relay-status";
import { normalizeRelayUrl } from "../../helpers/url"; import { normalizeRelayUrl } from "../../helpers/url";
import { RelayScoreBreakdown } from "../../components/relay-score-breakdown"; import { RelayScoreBreakdown } from "../../components/relay-score-breakdown";
import RequireCurrentAccount from "../../providers/require-current-account";
export default function RelaysView() { function RelaysPage() {
const relays = useSubject(clientRelaysService.relays); const relays = useSubject(clientRelaysService.relays);
const toast = useToast(); const toast = useToast();
@ -152,3 +153,11 @@ export default function RelaysView() {
</Flex> </Flex>
); );
} }
export default function RelaysView() {
return (
<RequireCurrentAccount>
<RelaysPage />
</RequireCurrentAccount>
);
}

View File

@ -29,7 +29,7 @@ export default function Header({
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey); const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
const account = useCurrentAccount(); const account = useCurrentAccount();
const isSelf = pubkey === account.pubkey; const isSelf = pubkey === account?.pubkey;
return ( return (
<Flex direction="column" gap="2" px="2" pt="2"> <Flex direction="column" gap="2" px="2" pt="2">