mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-10 04:39:19 +02:00
add basic accounts
This commit is contained in:
parent
8b660b4697
commit
621a1a2aea
13
src/app.tsx
13
src/app.tsx
@ -24,22 +24,25 @@ import NotificationsView from "./views/notifications";
|
||||
import { RelaysView } from "./views/relays";
|
||||
import useSubject from "./hooks/use-subject";
|
||||
import { LoginNip05View } from "./views/login/nip05";
|
||||
import { Spinner } from "@chakra-ui/react";
|
||||
|
||||
const RequireSetup = ({ children }: { children: JSX.Element }) => {
|
||||
const RequireAccount = ({ children }: { children: JSX.Element }) => {
|
||||
let location = useLocation();
|
||||
const setup = useSubject(accountService.setup);
|
||||
const loading = useSubject(accountService.loading);
|
||||
const account = useSubject(accountService.current);
|
||||
|
||||
if (!setup) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
|
||||
if (loading) return <Spinner />;
|
||||
if (!account) return <Navigate to="/login" state={{ from: location.pathname }} replace />;
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
const RootPage = () => (
|
||||
<RequireSetup>
|
||||
<RequireAccount>
|
||||
<Page>
|
||||
<Outlet />
|
||||
</Page>
|
||||
</RequireSetup>
|
||||
</RequireAccount>
|
||||
);
|
||||
|
||||
const router = createBrowserRouter([
|
||||
|
@ -1,6 +1,6 @@
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { NostrEvent } from "../types/nostr-event";
|
||||
import Deferred from "./deferred";
|
||||
import createDefer from "./deferred";
|
||||
import { IncomingCommandResult, Relay } from "./relay";
|
||||
import { ListenerFn, Subject } from "./subject";
|
||||
|
||||
@ -8,7 +8,7 @@ export type PostResult = { url: string; message?: string; status: boolean };
|
||||
|
||||
export function nostrPostAction(relays: string[], event: NostrEvent, timeout: number = 5000) {
|
||||
const subject = new Subject<PostResult>();
|
||||
const onComplete = new Deferred<void>();
|
||||
const onComplete = createDefer<void>();
|
||||
const remaining = new Map<Relay, ListenerFn<IncomingCommandResult>>();
|
||||
|
||||
for (const url of relays) {
|
||||
|
@ -3,7 +3,7 @@ import { NostrQuery } from "../types/nostr-query";
|
||||
import relayPoolService from "../services/relay-pool";
|
||||
import { IncomingEOSE, IncomingEvent, Relay } from "./relay";
|
||||
import Subject from "./subject";
|
||||
import Deferred from "./deferred";
|
||||
import createDefer from "./deferred";
|
||||
|
||||
let lastId = 0;
|
||||
|
||||
@ -19,7 +19,7 @@ export class NostrRequest {
|
||||
relayCleanup = new Map<Relay, Function>();
|
||||
state = NostrRequest.IDLE;
|
||||
onEvent = new Subject<NostrEvent>();
|
||||
onComplete = new Deferred<void>();
|
||||
onComplete = createDefer<void>();
|
||||
seenEvents = new Set<string>();
|
||||
|
||||
constructor(relayUrls: string[], timeout?: number) {
|
||||
|
@ -8,7 +8,6 @@ import { Bech32Prefix, normalizeToBech32 } from "../../helpers/nip-19";
|
||||
|
||||
import { NoteContents } from "./note-contents";
|
||||
import { NoteMenu } from "./note-menu";
|
||||
import accountService from "../../services/account";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import { UserTipButton } from "../user-tip-button";
|
||||
import { NoteRelays } from "./note-relays";
|
||||
@ -18,9 +17,8 @@ import { ReplyIcon } from "../icons";
|
||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||
import { buildReply } from "../../helpers/nostr-event";
|
||||
import { UserDnsIdentityIcon } from "../user-dns-identity";
|
||||
import { useReadonlyMode } from "../../hooks/use-readonly-mode";
|
||||
import { convertTimestampToDate } from "../../helpers/date";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
|
||||
export type NoteProps = {
|
||||
event: NostrEvent;
|
||||
@ -28,11 +26,10 @@ export type NoteProps = {
|
||||
};
|
||||
export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const readonly = useReadonlyMode();
|
||||
const account = useCurrentAccount();
|
||||
const { openModal } = useContext(PostModalContext);
|
||||
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const contacts = useUserContacts(pubkey);
|
||||
const contacts = useUserContacts(account.pubkey);
|
||||
const following = contacts?.contacts || [];
|
||||
|
||||
const reply = () => openModal(buildReply(event));
|
||||
@ -63,7 +60,7 @@ export const Note = React.memo(({ event, maxHeight }: NoteProps) => {
|
||||
aria-label="Reply"
|
||||
onClick={reply}
|
||||
size="xs"
|
||||
isDisabled={readonly}
|
||||
isDisabled={account.readonly}
|
||||
/>
|
||||
<Box flexGrow={1} />
|
||||
<UserTipButton pubkey={event.pubkey} size="xs" />
|
||||
|
@ -13,16 +13,15 @@ import { PostModalProvider } from "../providers/post-modal-provider";
|
||||
import { useReadonlyMode } from "../hooks/use-readonly-mode";
|
||||
import { ProfileButton } from "./profile-button";
|
||||
import { UserAvatarLink } from "./user-avatar-link";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import { useCurrentAccount } from "../hooks/use-current-account";
|
||||
|
||||
const MobileProfileHeader = () => {
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const readonly = useReadonlyMode();
|
||||
const account = useCurrentAccount();
|
||||
|
||||
return (
|
||||
<Flex justifyContent="space-between" padding="2" alignItems="center">
|
||||
<UserAvatarLink pubkey={pubkey} size="sm" />
|
||||
{readonly && (
|
||||
<UserAvatarLink pubkey={account.pubkey} size="sm" />
|
||||
{account.readonly && (
|
||||
<Button
|
||||
colorScheme="red"
|
||||
textAlign="center"
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { Box, LinkBox, Text } from "@chakra-ui/react";
|
||||
import { Link } from "react-router-dom";
|
||||
import accountService from "../services/account";
|
||||
import { UserAvatar } from "./user-avatar";
|
||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||
import { normalizeToBech32 } from "../helpers/nip-19";
|
||||
import { truncatedId } from "../helpers/nostr-event";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import { useCurrentAccount } from "../hooks/use-current-account";
|
||||
|
||||
export const ProfileButton = () => {
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const { pubkey } = useCurrentAccount();
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Button, ButtonProps } from "@chakra-ui/react";
|
||||
import { useReadonlyMode } from "../hooks/use-readonly-mode";
|
||||
import { useCurrentAccount } from "../hooks/use-current-account";
|
||||
import useSubject from "../hooks/use-subject";
|
||||
import clientFollowingService from "../services/client-following";
|
||||
|
||||
@ -7,7 +7,7 @@ export const UserFollowButton = ({
|
||||
pubkey,
|
||||
...props
|
||||
}: { pubkey: string } & Omit<ButtonProps, "onClick" | "isLoading" | "isDisabled">) => {
|
||||
const readonly = useReadonlyMode();
|
||||
const account = useCurrentAccount();
|
||||
const following = useSubject(clientFollowingService.following) ?? [];
|
||||
const savingDraft = useSubject(clientFollowingService.savingDraft);
|
||||
|
||||
@ -24,7 +24,7 @@ export const UserFollowButton = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button colorScheme="brand" {...props} isLoading={savingDraft} onClick={toggleFollow} isDisabled={readonly}>
|
||||
<Button colorScheme="brand" {...props} isLoading={savingDraft} onClick={toggleFollow} isDisabled={account.readonly}>
|
||||
{isFollowing ? "Unfollow" : "Follow"}
|
||||
</Button>
|
||||
);
|
||||
|
8
src/hooks/use-current-account.ts
Normal file
8
src/hooks/use-current-account.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import accountService from "../services/account";
|
||||
import useSubject from "./use-subject";
|
||||
|
||||
export function useCurrentAccount() {
|
||||
const account = useSubject(accountService.current);
|
||||
if (!account) throw Error("no account");
|
||||
return account;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import accountService from "../services/account";
|
||||
import useSubject from "./use-subject";
|
||||
import { useCurrentAccount } from "./use-current-account";
|
||||
|
||||
/** @deprecated */
|
||||
export function useReadonlyMode() {
|
||||
return useSubject(accountService.readonly);
|
||||
const account = useCurrentAccount();
|
||||
return account.readonly;
|
||||
}
|
||||
|
@ -1,83 +1,59 @@
|
||||
import { PersistentSubject, Subject } from "../classes/subject";
|
||||
import settings from "./settings";
|
||||
import { PersistentSubject } from "../classes/subject";
|
||||
import db from "./db";
|
||||
|
||||
export type PresetRelays = Record<string, { read: boolean; write: boolean }>;
|
||||
|
||||
export type SavedIdentity = {
|
||||
export type Account = {
|
||||
pubkey: string;
|
||||
secKey?: string;
|
||||
useExtension: boolean;
|
||||
readonly: boolean;
|
||||
relays?: string[];
|
||||
};
|
||||
|
||||
class AccountService {
|
||||
loading = new PersistentSubject(false);
|
||||
setup = new PersistentSubject(false);
|
||||
pubkey = new Subject<string>();
|
||||
readonly = new PersistentSubject(false);
|
||||
// directory of relays provided by nip07 extension
|
||||
relays = new Subject<PresetRelays>({});
|
||||
private useExtension: boolean = false;
|
||||
private secKey: string | undefined = undefined;
|
||||
loading = new PersistentSubject(true);
|
||||
accounts = new PersistentSubject<Account[]>([]);
|
||||
current = new PersistentSubject<Account | null>(null);
|
||||
|
||||
constructor() {
|
||||
settings.identity.subscribe((savedIdentity) => {
|
||||
this.loading.next(false);
|
||||
if (savedIdentity) {
|
||||
this.setup.next(true);
|
||||
this.pubkey.next(savedIdentity.pubkey);
|
||||
this.readonly.next(false);
|
||||
this.secKey = savedIdentity.secKey;
|
||||
this.useExtension = savedIdentity.useExtension;
|
||||
} else {
|
||||
this.setup.next(false);
|
||||
this.pubkey.next("");
|
||||
this.readonly.next(false);
|
||||
this.secKey = undefined;
|
||||
this.useExtension = false;
|
||||
db.getAll("accounts").then((accounts) => {
|
||||
this.accounts.next(accounts);
|
||||
|
||||
const lastAccount = localStorage.getItem("lastAccount");
|
||||
if (lastAccount && this.hasAccount(lastAccount)) {
|
||||
this.switchAccount(lastAccount);
|
||||
}
|
||||
|
||||
this.loading.next(false);
|
||||
});
|
||||
}
|
||||
|
||||
async loginWithExtension() {
|
||||
if (window.nostr) {
|
||||
try {
|
||||
this.loading.next(true);
|
||||
const pubkey = await window.nostr.getPublicKey();
|
||||
const relays = await window.nostr.getRelays();
|
||||
hasAccount(pubkey: string) {
|
||||
return this.accounts.value.some((acc) => acc.pubkey === pubkey);
|
||||
}
|
||||
addAccount(pubkey: string, relays?: string[], readonly = false) {
|
||||
const account: Account = { pubkey, relays, readonly };
|
||||
this.accounts.next(this.accounts.value.concat(account));
|
||||
|
||||
if (Array.isArray(relays)) {
|
||||
this.relays.next(relays.reduce<PresetRelays>((d, r) => ({ ...d, [r]: { read: true, write: true } }), {}));
|
||||
} else {
|
||||
this.relays.next(relays);
|
||||
}
|
||||
db.put("accounts", account);
|
||||
}
|
||||
removeAccount(pubkey: string) {
|
||||
this.accounts.next(this.accounts.value.filter((acc) => acc.pubkey !== pubkey));
|
||||
|
||||
settings.identity.next({
|
||||
pubkey,
|
||||
useExtension: true,
|
||||
});
|
||||
} catch (e) {
|
||||
this.loading.next(false);
|
||||
}
|
||||
}
|
||||
db.delete("accounts", pubkey);
|
||||
}
|
||||
|
||||
// loginWithSecKey(secKey: string) {
|
||||
// const pubkey =
|
||||
// settings.identity.next({
|
||||
// pubkey,
|
||||
// useExtension: true,
|
||||
// });
|
||||
// }
|
||||
|
||||
loginWithPubkey(pubkey: string) {
|
||||
this.readonly.next(true);
|
||||
this.pubkey.next(pubkey);
|
||||
this.setup.next(true);
|
||||
this.loading.next(false);
|
||||
switchAccount(pubkey: string) {
|
||||
const account = this.accounts.value.find((acc) => acc.pubkey === pubkey);
|
||||
if (account) {
|
||||
this.current.next(account);
|
||||
localStorage.setItem("lastAccount", pubkey);
|
||||
}
|
||||
}
|
||||
switchToTemporary(account: Account) {
|
||||
this.current.next(account);
|
||||
}
|
||||
|
||||
logout() {
|
||||
settings.identity.next(null);
|
||||
this.current.next(null);
|
||||
localStorage.removeItem("lastAccount");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,19 +29,20 @@ function handleNewContacts(contacts: UserContacts | undefined) {
|
||||
|
||||
let sub: Subject<UserContacts> | undefined;
|
||||
function updateSub() {
|
||||
const pubkey = accountService.current.value?.pubkey;
|
||||
if (sub) {
|
||||
sub.unsubscribe(handleNewContacts);
|
||||
sub = undefined;
|
||||
}
|
||||
|
||||
if (accountService.pubkey.value) {
|
||||
sub = userContactsService.requestContacts(accountService.pubkey.value, clientRelaysService.getReadUrls(), true);
|
||||
if (pubkey) {
|
||||
sub = userContactsService.requestContacts(pubkey, clientRelaysService.getReadUrls(), true);
|
||||
|
||||
sub.subscribe(handleNewContacts);
|
||||
}
|
||||
}
|
||||
|
||||
accountService.pubkey.subscribe(() => {
|
||||
accountService.current.subscribe(() => {
|
||||
// clear the following list until a new one can be fetched
|
||||
following.next([]);
|
||||
|
||||
|
@ -23,29 +23,28 @@ class ClientRelayService {
|
||||
|
||||
constructor() {
|
||||
let lastSubject: Subject<UserRelays> | undefined;
|
||||
accountService.pubkey.subscribe((pubkey) => {
|
||||
// clear the relay list until a new one can be fetched
|
||||
// this.relays.next([]);
|
||||
accountService.current.subscribe((account) => {
|
||||
this.relays.next([]);
|
||||
|
||||
if (!account) return;
|
||||
|
||||
if (account.relays) {
|
||||
this.bootstrapRelays.clear();
|
||||
for (const relay of account.relays) {
|
||||
this.bootstrapRelays.add(relay);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastSubject) {
|
||||
lastSubject.unsubscribe(this.handleRelayChanged, this);
|
||||
lastSubject = undefined;
|
||||
}
|
||||
|
||||
lastSubject = userRelaysService.requestRelays(pubkey, Array.from(this.bootstrapRelays), true);
|
||||
lastSubject = userRelaysService.requestRelays(account.pubkey, Array.from(this.bootstrapRelays), true);
|
||||
|
||||
lastSubject.subscribe(this.handleRelayChanged, this);
|
||||
});
|
||||
|
||||
// add preset relays fromm nip07 extension to bootstrap list
|
||||
accountService.relays.subscribe((presetRelays) => {
|
||||
for (const [url, opts] of Object.entries(presetRelays)) {
|
||||
if (opts.read) {
|
||||
clientRelaysService.bootstrapRelays.add(url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.relays.subscribe((relays) => this.writeRelays.next(relays.filter((r) => r.mode & RelayMode.WRITE)));
|
||||
this.relays.subscribe((relays) => this.readRelays.next(relays.filter((r) => r.mode & RelayMode.READ)));
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ const MIGRATIONS: MigrationFunction[] = [
|
||||
|
||||
db.createObjectStore("settings");
|
||||
db.createObjectStore("relayInfo");
|
||||
db.createObjectStore("accounts", { keyPath: "pubkey" });
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { DBSchema } from "idb";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import { Account } from "../account";
|
||||
import { RelayInformationDocument } from "../relay-info";
|
||||
|
||||
export interface CustomSchema extends DBSchema {
|
||||
@ -38,4 +39,8 @@ export interface CustomSchema extends DBSchema {
|
||||
key: string;
|
||||
value: any;
|
||||
};
|
||||
accounts: {
|
||||
key: string;
|
||||
value: Account;
|
||||
};
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { PersistentSubject } from "../classes/subject";
|
||||
import db from "./db";
|
||||
import { SavedIdentity } from "./account";
|
||||
import { Account } from "./account";
|
||||
|
||||
const settings = {
|
||||
identity: new PersistentSubject<SavedIdentity | null>(null),
|
||||
blurImages: new PersistentSubject(true),
|
||||
autoShowMedia: new PersistentSubject(true),
|
||||
proxyUserMedia: new PersistentSubject(false),
|
||||
accounts: new PersistentSubject<Account[]>([]),
|
||||
};
|
||||
|
||||
async function loadSettings() {
|
||||
@ -15,6 +15,7 @@ async function loadSettings() {
|
||||
// load
|
||||
for (const [key, subject] of Object.entries(settings)) {
|
||||
const value = await db.get("settings", key);
|
||||
// @ts-ignore
|
||||
if (value !== undefined) subject.next(value);
|
||||
|
||||
// save
|
||||
|
@ -3,13 +3,11 @@ import { Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import moment from "moment";
|
||||
import { Note } from "../../components/note";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import accountService from "../../services/account";
|
||||
import userContactsService from "../../services/user-contacts";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
|
||||
function useExtendedContacts(pubkey: string) {
|
||||
const readRelays = useReadRelayUrls();
|
||||
@ -41,10 +39,10 @@ function useExtendedContacts(pubkey: string) {
|
||||
|
||||
export const DiscoverTab = () => {
|
||||
useAppTitle("discover");
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const account = useCurrentAccount();
|
||||
const relays = useReadRelayUrls();
|
||||
|
||||
const contactsOfContacts = useExtendedContacts(pubkey);
|
||||
const contactsOfContacts = useExtendedContacts(account.pubkey);
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
`discover`,
|
||||
relays,
|
||||
|
@ -5,20 +5,17 @@ import { Note } from "../../components/note";
|
||||
import { isNote } from "../../helpers/nostr-event";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
import { useUserContacts } from "../../hooks/use-user-contacts";
|
||||
import accountService from "../../services/account";
|
||||
import { AddIcon } from "@chakra-ui/icons";
|
||||
import { useContext } from "react";
|
||||
import { PostModalContext } from "../../providers/post-modal-provider";
|
||||
import { useReadonlyMode } from "../../hooks/use-readonly-mode";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
|
||||
export const FollowingTab = () => {
|
||||
const readonly = useReadonlyMode();
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const account = useCurrentAccount();
|
||||
const relays = useReadRelayUrls();
|
||||
const { openModal } = useContext(PostModalContext);
|
||||
const contacts = useUserContacts(pubkey);
|
||||
const contacts = useUserContacts(account.pubkey);
|
||||
const [search, setSearch] = useSearchParams();
|
||||
const showReplies = search.has("replies");
|
||||
const onToggle = () => {
|
||||
@ -37,7 +34,7 @@ export const FollowingTab = () => {
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="2">
|
||||
<Button variant="outline" leftIcon={<AddIcon />} onClick={() => openModal()} isDisabled={readonly}>
|
||||
<Button variant="outline" leftIcon={<AddIcon />} onClick={() => openModal()} isDisabled={account.readonly}>
|
||||
New Post
|
||||
</Button>
|
||||
<FormControl display="flex" alignItems="center">
|
||||
|
@ -4,10 +4,10 @@ import useSubject from "../../hooks/use-subject";
|
||||
import accountService from "../../services/account";
|
||||
|
||||
export const LoginView = () => {
|
||||
const setup = useSubject(accountService.setup);
|
||||
const current = useSubject(accountService.current);
|
||||
const location = useLocation();
|
||||
|
||||
if (setup) return <Navigate to={location.state?.from ?? "/"} replace />;
|
||||
if (current) return <Navigate to={location.state?.from ?? "/"} replace />;
|
||||
|
||||
return (
|
||||
<Flex direction="column" alignItems="center" justifyContent="center" gap="4" height="80%" px="4">
|
||||
|
@ -64,16 +64,21 @@ export const LoginNip05View = () => {
|
||||
return toast({ status: "error", title: "No relay selected" });
|
||||
}
|
||||
|
||||
accountService.loginWithPubkey(pubkey);
|
||||
// add the account if it dose not exist
|
||||
if (!accountService.hasAccount(pubkey)) {
|
||||
const bootstrapRelays = new Set<string>();
|
||||
|
||||
if (relayUrl) {
|
||||
clientRelaysService.bootstrapRelays.add(relayUrl);
|
||||
}
|
||||
if (relays) {
|
||||
for (const url of relays) {
|
||||
clientRelaysService.bootstrapRelays.add(url);
|
||||
if (relayUrl) bootstrapRelays.add(relayUrl);
|
||||
if (relays) {
|
||||
for (const url of relays) {
|
||||
bootstrapRelays.add(url);
|
||||
}
|
||||
}
|
||||
|
||||
accountService.addAccount(pubkey, Array.from(bootstrapRelays), true);
|
||||
}
|
||||
|
||||
accountService.switchAccount(pubkey);
|
||||
};
|
||||
|
||||
const renderInputIcon = () => {
|
||||
|
@ -20,7 +20,10 @@ export const LoginNpubView = () => {
|
||||
return toast({ status: "error", title: "Invalid npub" });
|
||||
}
|
||||
|
||||
accountService.loginWithPubkey(pubkey);
|
||||
if (!accountService.hasAccount(pubkey)) {
|
||||
accountService.addAccount(pubkey, [relayUrl], true);
|
||||
}
|
||||
accountService.switchAccount(pubkey);
|
||||
|
||||
clientRelaysService.bootstrapRelays.add(relayUrl);
|
||||
};
|
||||
|
@ -1,11 +1,39 @@
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Button, Spinner } from "@chakra-ui/react";
|
||||
import { Alert, AlertDescription, AlertIcon, AlertTitle, Box, Button, Flex, Spinner } from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import accountService from "../../services/account";
|
||||
|
||||
export const LoginStartView = () => {
|
||||
const navigate = useNavigate();
|
||||
const loading = useSubject(accountService.loading);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const accounts = useSubject(accountService.accounts);
|
||||
|
||||
const loginWithExtension = async () => {
|
||||
if (window.nostr) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const pubkey = await window.nostr.getPublicKey();
|
||||
|
||||
if (!accountService.hasAccount(pubkey)) {
|
||||
let relays: string[] = [];
|
||||
const extRelays = await window.nostr.getRelays();
|
||||
if (Array.isArray(extRelays)) {
|
||||
relays = extRelays;
|
||||
} else {
|
||||
relays = Object.keys(extRelays).filter((url) => extRelays[url].read);
|
||||
}
|
||||
|
||||
accountService.addAccount(pubkey, relays, false);
|
||||
}
|
||||
|
||||
accountService.switchAccount(pubkey);
|
||||
} catch (e) {}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) return <Spinner />;
|
||||
|
||||
return (
|
||||
@ -17,13 +45,20 @@ export const LoginStartView = () => {
|
||||
<AlertDescription>There are bugs and things will break.</AlertDescription>
|
||||
</Box>
|
||||
</Alert>
|
||||
<Button onClick={() => accountService.loginWithExtension()} colorScheme="brand">
|
||||
<Button onClick={loginWithExtension} colorScheme="brand">
|
||||
Use browser extension
|
||||
</Button>
|
||||
<Button onClick={() => navigate("./nip05")}>Login with Nip-05 Id</Button>
|
||||
<Button variant="link" onClick={() => navigate("./npub")}>
|
||||
Login with npub
|
||||
</Button>
|
||||
<Flex gap="2" direction="column">
|
||||
{accounts.map((account) => (
|
||||
<Button key={account.pubkey} onClick={() => accountService.switchAccount(account.pubkey)}>
|
||||
{account.pubkey}
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -6,9 +6,8 @@ import { UserAvatar } from "../../components/user-avatar";
|
||||
import { UserLink } from "../../components/user-link";
|
||||
import { convertTimestampToDate } from "../../helpers/date";
|
||||
import { useReadRelayUrls } from "../../hooks/use-client-relays";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import { useTimelineLoader } from "../../hooks/use-timeline-loader";
|
||||
import accountService from "../../services/account";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
|
||||
const Kind1Notification = ({ event }: { event: NostrEvent }) => {
|
||||
@ -39,12 +38,12 @@ const NotificationItem = memo(({ event }: { event: NostrEvent }) => {
|
||||
|
||||
const NotificationsView = () => {
|
||||
const readRelays = useReadRelayUrls();
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const account = useCurrentAccount();
|
||||
const { events, loading, loadMore } = useTimelineLoader(
|
||||
"notifications",
|
||||
readRelays,
|
||||
{
|
||||
"#p": [pubkey],
|
||||
"#p": [account.pubkey],
|
||||
kinds: [1],
|
||||
since: moment().subtract(1, "day").unix(),
|
||||
},
|
||||
@ -53,7 +52,7 @@ const NotificationsView = () => {
|
||||
|
||||
const timeline = events
|
||||
// ignore events made my the user
|
||||
.filter((e) => e.pubkey !== pubkey);
|
||||
.filter((e) => e.pubkey !== account.pubkey);
|
||||
|
||||
return (
|
||||
<Flex direction="column" overflowX="hidden" overflowY="auto" gap="2">
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Avatar, Button, Flex, FormControl, FormLabel, Input, SkeletonText, Textarea } from "@chakra-ui/react";
|
||||
import { useMemo } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||
import accountService from "../../services/account";
|
||||
@ -60,8 +61,8 @@ const MetadataForm = ({ defaultValues, onSubmit }: MetadataFormProps) => {
|
||||
};
|
||||
|
||||
export const ProfileEditView = () => {
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
const metadata = useUserMetadata(pubkey);
|
||||
const account = useCurrentAccount();
|
||||
const metadata = useUserMetadata(account.pubkey);
|
||||
|
||||
const defaultValues = useMemo<FormData>(
|
||||
() => ({
|
||||
|
@ -1,9 +1,5 @@
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import accountService from "../../services/account";
|
||||
import { ProfileEditView } from "./edit";
|
||||
|
||||
export const ProfileView = () => {
|
||||
const pubkey = useSubject(accountService.pubkey) ?? "";
|
||||
|
||||
return <ProfileEditView />;
|
||||
};
|
||||
|
@ -13,8 +13,7 @@ export const UserProfileMenu = ({ pubkey, ...props }: { pubkey: string } & Omit<
|
||||
|
||||
const loginAsUser = () => {
|
||||
if (confirm(`Do you want to logout and login as ${getUserDisplayName(metadata, pubkey)}?`)) {
|
||||
accountService.logout();
|
||||
accountService.loginWithPubkey(pubkey);
|
||||
accountService.switchToTemporary({ pubkey, readonly: true });
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -27,6 +27,7 @@ import { CopyIconButton } from "../../components/copy-icon-button";
|
||||
import accountService from "../../services/account";
|
||||
import { UserFollowButton } from "../../components/user-follow-button";
|
||||
import { useAppTitle } from "../../hooks/use-app-title";
|
||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||
|
||||
const tabs = [
|
||||
{ label: "Notes", path: "notes" },
|
||||
@ -39,6 +40,7 @@ const tabs = [
|
||||
const UserView = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const navigate = useNavigate();
|
||||
const account = useCurrentAccount();
|
||||
const { pubkey } = useLoaderData() as { pubkey: string };
|
||||
|
||||
const matches = useMatches();
|
||||
@ -48,7 +50,7 @@ const UserView = () => {
|
||||
|
||||
const metadata = useUserMetadata(pubkey, [], true);
|
||||
const npub = normalizeToBech32(pubkey, Bech32Prefix.Pubkey);
|
||||
const isSelf = pubkey === accountService.pubkey.value;
|
||||
const isSelf = pubkey === account.pubkey;
|
||||
|
||||
useAppTitle(getUserDisplayName(metadata, npub ?? pubkey));
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user