feat: multi-account system
This commit is contained in:
@ -1,19 +1,17 @@
|
||||
import "./ChatPage.css";
|
||||
import { KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { RawEvent, TaggedRawEvent } from "@snort/nostr";
|
||||
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import { bech32ToHex } from "Util";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
|
||||
import DM from "Element/DM";
|
||||
import { RawEvent, TaggedRawEvent } from "@snort/nostr";
|
||||
import { dmsInChat, isToSelf } from "Pages/MessagesPage";
|
||||
import NoteToSelf from "Element/NoteToSelf";
|
||||
import { RootState } from "State/Store";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
type RouterParams = {
|
||||
id: string;
|
||||
@ -23,7 +21,7 @@ export default function ChatPage() {
|
||||
const params = useParams<RouterParams>();
|
||||
const publisher = useEventPublisher();
|
||||
const id = bech32ToHex(params.id ?? "");
|
||||
const pubKey = useSelector((s: RootState) => s.login.publicKey);
|
||||
const pubKey = useLogin().publicKey;
|
||||
const [content, setContent] = useState<string>();
|
||||
const dmListRef = useRef<HTMLDivElement>(null);
|
||||
const dms = filterDms(useDmCache());
|
||||
|
@ -1,30 +1,27 @@
|
||||
import { useMemo } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
|
||||
import Timeline from "Element/Timeline";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { setTags } from "State/Login";
|
||||
import type { RootState } from "State/Store";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { setTags } from "Login";
|
||||
|
||||
const HashTagsPage = () => {
|
||||
const params = useParams();
|
||||
const tag = (params.tag ?? "").toLowerCase();
|
||||
const dispatch = useDispatch();
|
||||
const { tags } = useSelector((s: RootState) => s.login);
|
||||
const login = useLogin();
|
||||
const isFollowing = useMemo(() => {
|
||||
return tags.includes(tag);
|
||||
}, [tags, tag]);
|
||||
return login.tags.item.includes(tag);
|
||||
}, [login, tag]);
|
||||
const publisher = useEventPublisher();
|
||||
|
||||
function followTags(ts: string[]) {
|
||||
dispatch(
|
||||
setTags({
|
||||
tags: ts,
|
||||
createdAt: new Date().getTime(),
|
||||
})
|
||||
);
|
||||
publisher.tags(ts).then(ev => publisher.broadcast(ev));
|
||||
async function followTags(ts: string[]) {
|
||||
const ev = await publisher.tags(ts);
|
||||
if (ev) {
|
||||
publisher.broadcast(ev);
|
||||
setTags(login, ts, ev.created_at * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@ -33,11 +30,14 @@ const HashTagsPage = () => {
|
||||
<div className="action-heading">
|
||||
<h2>#{tag}</h2>
|
||||
{isFollowing ? (
|
||||
<button type="button" className="secondary" onClick={() => followTags(tags.filter(t => t !== tag))}>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary"
|
||||
onClick={() => followTags(login.tags.item.filter(t => t !== tag))}>
|
||||
<FormattedMessage defaultMessage="Unfollow" />
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={() => followTags(tags.concat([tag]))}>
|
||||
<button type="button" onClick={() => followTags(login.tags.item.concat([tag]))}>
|
||||
<FormattedMessage defaultMessage="Follow" />
|
||||
</button>
|
||||
)}
|
||||
|
@ -4,13 +4,10 @@ import { useDispatch, useSelector } from "react-redux";
|
||||
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
import { RelaySettings } from "@snort/nostr";
|
||||
import messages from "./messages";
|
||||
|
||||
import { bech32ToHex, randomSample, unixNowMs, unwrap } from "Util";
|
||||
import Icon from "Icons/Icon";
|
||||
import { RootState } from "State/Store";
|
||||
import { init, setRelays } from "State/Login";
|
||||
import { setShow, reset } from "State/NoteCreator";
|
||||
import { System } from "System";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
@ -20,11 +17,11 @@ import useModeration from "Hooks/useModeration";
|
||||
import { NoteCreator } from "Element/NoteCreator";
|
||||
import { db } from "Db";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { DefaultRelays, SnortPubKey } from "Const";
|
||||
import SubDebug from "Element/SubDebug";
|
||||
import { preload } from "Cache";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
import { mapPlanName } from "./subscribe";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
export default function Layout() {
|
||||
const location = useLocation();
|
||||
@ -33,9 +30,7 @@ export default function Layout() {
|
||||
const isReplyNoteCreatorShowing = replyTo && isNoteCreatorShowing;
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { loggedOut, publicKey, relays, preferences, newUserKey, subscription } = useSelector(
|
||||
(s: RootState) => s.login
|
||||
);
|
||||
const { publicKey, relays, preferences, currentSubscription } = useLogin();
|
||||
const [pageClass, setPageClass] = useState("page");
|
||||
const pub = useEventPublisher();
|
||||
useLoginFeed();
|
||||
@ -72,11 +67,11 @@ export default function Layout() {
|
||||
useEffect(() => {
|
||||
if (relays) {
|
||||
(async () => {
|
||||
for (const [k, v] of Object.entries(relays)) {
|
||||
for (const [k, v] of Object.entries(relays.item)) {
|
||||
await System.ConnectToRelay(k, v);
|
||||
}
|
||||
for (const [k, c] of System.Sockets) {
|
||||
if (!relays[k] && !c.Ephemeral) {
|
||||
if (!relays.item[k] && !c.Ephemeral) {
|
||||
System.DisconnectRelay(k);
|
||||
}
|
||||
}
|
||||
@ -117,7 +112,6 @@ export default function Layout() {
|
||||
await preload();
|
||||
}
|
||||
console.debug(`Using db: ${a ? "IndexedDB" : "In-Memory"}`);
|
||||
dispatch(init());
|
||||
|
||||
try {
|
||||
if ("registerProtocolHandler" in window.navigator) {
|
||||
@ -133,53 +127,16 @@ export default function Layout() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
async function handleNewUser() {
|
||||
let newRelays: Record<string, RelaySettings> = {};
|
||||
|
||||
try {
|
||||
const rsp = await fetch("https://api.nostr.watch/v1/online");
|
||||
if (rsp.ok) {
|
||||
const online: string[] = await rsp.json();
|
||||
const pickRandom = randomSample(online, 4);
|
||||
const relayObjects = pickRandom.map(a => [a, { read: true, write: true }]);
|
||||
newRelays = {
|
||||
...Object.fromEntries(relayObjects),
|
||||
...Object.fromEntries(DefaultRelays.entries()),
|
||||
};
|
||||
dispatch(
|
||||
setRelays({
|
||||
relays: newRelays,
|
||||
createdAt: unixNowMs(),
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
const ev = await pub.addFollow([bech32ToHex(SnortPubKey), unwrap(publicKey)], newRelays);
|
||||
pub.broadcast(ev);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (newUserKey === true) {
|
||||
handleNewUser().catch(console.warn);
|
||||
}
|
||||
}, [newUserKey]);
|
||||
|
||||
if (typeof loggedOut !== "boolean") {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className={pageClass}>
|
||||
{!shouldHideHeader && (
|
||||
<header>
|
||||
<div className="logo" onClick={() => navigate("/")}>
|
||||
<h1>Snort</h1>
|
||||
{subscription && (
|
||||
{currentSubscription && (
|
||||
<small className="flex">
|
||||
<Icon name="diamond" size={10} className="mr5" />
|
||||
{mapPlanName(subscription.type)}
|
||||
{mapPlanName(currentSubscription.type)}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
@ -214,7 +171,7 @@ const AccountHeader = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { isMuted } = useModeration();
|
||||
const { publicKey, latestNotification, readNotifications } = useSelector((s: RootState) => s.login);
|
||||
const { publicKey, latestNotification, readNotifications } = useLogin();
|
||||
const dms = useDmCache();
|
||||
|
||||
const hasNotifications = useMemo(
|
||||
|
@ -1,22 +1,23 @@
|
||||
import "./Login.css";
|
||||
|
||||
import { CSSProperties, useEffect, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import * as secp from "@noble/secp256k1";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
|
||||
import { RootState } from "State/Store";
|
||||
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||
import { DefaultRelays, EmailRegex, MnemonicRegex } from "Const";
|
||||
import { EmailRegex, MnemonicRegex } from "Const";
|
||||
import { bech32ToHex, unwrap } from "Util";
|
||||
import { generateBip39Entropy, entropyToDerivedKey } from "nip6";
|
||||
import ZapButton from "Element/ZapButton";
|
||||
import useImgProxy from "Hooks/useImgProxy";
|
||||
import Icon from "Icons/Icon";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { generateNewLogin, LoginStore } from "Login";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
|
||||
import messages from "./messages";
|
||||
import Icon from "Icons/Icon";
|
||||
|
||||
interface ArtworkEntry {
|
||||
name: string;
|
||||
@ -24,26 +25,28 @@ interface ArtworkEntry {
|
||||
link: string;
|
||||
}
|
||||
|
||||
const KarnageKey = bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac");
|
||||
|
||||
// todo: fill more
|
||||
const Artwork: Array<ArtworkEntry> = [
|
||||
{
|
||||
name: "",
|
||||
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
|
||||
pubkey: KarnageKey,
|
||||
link: "https://void.cat/d/VKhPayp9ekeXYZGzAL9CxP",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
|
||||
pubkey: KarnageKey,
|
||||
link: "https://void.cat/d/3H2h8xxc3aEN6EVeobd8tw",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
|
||||
pubkey: KarnageKey,
|
||||
link: "https://void.cat/d/7i9W9PXn3TV86C4RUefNC9",
|
||||
},
|
||||
{
|
||||
name: "",
|
||||
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
|
||||
pubkey: KarnageKey,
|
||||
link: "https://void.cat/d/KtoX4ei6RYHY7HESg3Ve3k",
|
||||
},
|
||||
];
|
||||
@ -64,9 +67,9 @@ export async function getNip05PubKey(addr: string): Promise<string> {
|
||||
}
|
||||
|
||||
export default function LoginPage() {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const publisher = useEventPublisher();
|
||||
const login = useLogin();
|
||||
const [key, setKey] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [art, setArt] = useState<ArtworkEntry>();
|
||||
@ -77,10 +80,10 @@ export default function LoginPage() {
|
||||
const hasSubtleCrypto = window.crypto.subtle !== undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (publicKey) {
|
||||
if (login.publicKey) {
|
||||
navigate("/");
|
||||
}
|
||||
}, [publicKey, navigate]);
|
||||
}, [login, navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
const ret = unwrap(Artwork.at(Artwork.length * Math.random()));
|
||||
@ -99,28 +102,28 @@ export default function LoginPage() {
|
||||
}
|
||||
const hexKey = bech32ToHex(key);
|
||||
if (secp.utils.isValidPrivateKey(hexKey)) {
|
||||
dispatch(setPrivateKey(hexKey));
|
||||
LoginStore.loginWithPrivateKey(hexKey);
|
||||
} else {
|
||||
throw new Error("INVALID PRIVATE KEY");
|
||||
}
|
||||
} else if (key.startsWith("npub")) {
|
||||
const hexKey = bech32ToHex(key);
|
||||
dispatch(setPublicKey(hexKey));
|
||||
LoginStore.loginWithPubkey(hexKey);
|
||||
} else if (key.match(EmailRegex)) {
|
||||
const hexKey = await getNip05PubKey(key);
|
||||
dispatch(setPublicKey(hexKey));
|
||||
LoginStore.loginWithPubkey(hexKey);
|
||||
} else if (key.match(MnemonicRegex)) {
|
||||
if (!hasSubtleCrypto) {
|
||||
throw new Error(insecureMsg);
|
||||
}
|
||||
const ent = generateBip39Entropy(key);
|
||||
const keyHex = entropyToDerivedKey(ent);
|
||||
dispatch(setPrivateKey(keyHex));
|
||||
LoginStore.loginWithPrivateKey(keyHex);
|
||||
} else if (secp.utils.isValidPrivateKey(key)) {
|
||||
if (!hasSubtleCrypto) {
|
||||
throw new Error(insecureMsg);
|
||||
}
|
||||
dispatch(setPrivateKey(key));
|
||||
LoginStore.loginWithPrivateKey(key);
|
||||
} else {
|
||||
throw new Error("INVALID PRIVATE KEY");
|
||||
}
|
||||
@ -139,29 +142,14 @@ export default function LoginPage() {
|
||||
}
|
||||
|
||||
async function makeRandomKey() {
|
||||
const ent = generateBip39Entropy();
|
||||
const entHex = secp.utils.bytesToHex(ent);
|
||||
const newKeyHex = entropyToDerivedKey(ent);
|
||||
dispatch(setGeneratedPrivateKey({ key: newKeyHex, entropy: entHex }));
|
||||
await generateNewLogin(publisher);
|
||||
navigate("/new");
|
||||
}
|
||||
|
||||
async function doNip07Login() {
|
||||
const relays = "getRelays" in window.nostr ? await window.nostr.getRelays() : undefined;
|
||||
const pubKey = await window.nostr.getPublicKey();
|
||||
dispatch(setPublicKey(pubKey));
|
||||
|
||||
if ("getRelays" in window.nostr) {
|
||||
const relays = await window.nostr.getRelays();
|
||||
dispatch(
|
||||
setRelays({
|
||||
relays: {
|
||||
...relays,
|
||||
...Object.fromEntries(DefaultRelays.entries()),
|
||||
},
|
||||
createdAt: 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
LoginStore.loginWithPubkey(pubKey, relays);
|
||||
}
|
||||
|
||||
function altLogins() {
|
||||
@ -198,9 +186,9 @@ export default function LoginPage() {
|
||||
/>
|
||||
</p>
|
||||
<div className="login-actions">
|
||||
<button type="button" onClick={() => makeRandomKey()}>
|
||||
<AsyncButton onClick={() => makeRandomKey()}>
|
||||
<FormattedMessage defaultMessage="Generate Key" description="Button: Generate a new key" />
|
||||
</button>
|
||||
</AsyncButton>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { useMemo } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { HexKey, RawEvent } from "@snort/nostr";
|
||||
|
||||
import UnreadCount from "Element/UnreadCount";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import { hexToBech32 } from "../Util";
|
||||
import { incDmInteraction } from "State/Login";
|
||||
import { RootState } from "State/Store";
|
||||
import { hexToBech32 } from "Util";
|
||||
import NoteToSelf from "Element/NoteToSelf";
|
||||
import useModeration from "Hooks/useModeration";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
import { useDmCache } from "Hooks/useDmsCache";
|
||||
|
||||
type DmChat = {
|
||||
pubkey: HexKey;
|
||||
@ -21,18 +19,19 @@ type DmChat = {
|
||||
};
|
||||
|
||||
export default function MessagesPage() {
|
||||
const dispatch = useDispatch();
|
||||
const myPubKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const dmInteraction = useSelector<RootState, number>(s => s.login.dmInteraction);
|
||||
const login = useLogin();
|
||||
const { isMuted } = useModeration();
|
||||
const dms = useDmCache();
|
||||
|
||||
const chats = useMemo(() => {
|
||||
return extractChats(
|
||||
dms.filter(a => !isMuted(a.pubkey)),
|
||||
myPubKey ?? ""
|
||||
);
|
||||
}, [dms, myPubKey, dmInteraction]);
|
||||
if (login.publicKey) {
|
||||
return extractChats(
|
||||
dms.filter(a => !isMuted(a.pubkey)),
|
||||
login.publicKey
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}, [dms, login]);
|
||||
|
||||
const unreadCount = useMemo(() => chats.reduce((p, c) => p + c.unreadMessages, 0), [chats]);
|
||||
|
||||
@ -50,7 +49,7 @@ export default function MessagesPage() {
|
||||
}
|
||||
|
||||
function person(chat: DmChat) {
|
||||
if (chat.pubkey === myPubKey) return noteToSelf(chat);
|
||||
if (chat.pubkey === login.publicKey) return noteToSelf(chat);
|
||||
return (
|
||||
<div className="flex mb10" key={chat.pubkey}>
|
||||
<ProfileImage pubkey={chat.pubkey} className="f-grow" link={`/messages/${hexToBech32("npub", chat.pubkey)}`} />
|
||||
@ -63,7 +62,6 @@ export default function MessagesPage() {
|
||||
for (const c of chats) {
|
||||
setLastReadDm(c.pubkey);
|
||||
}
|
||||
dispatch(incDmInteraction());
|
||||
}
|
||||
|
||||
return (
|
||||
@ -78,7 +76,11 @@ export default function MessagesPage() {
|
||||
</div>
|
||||
{chats
|
||||
.sort((a, b) => {
|
||||
return a.pubkey === myPubKey ? -1 : b.pubkey === myPubKey ? 1 : b.newestMessage - a.newestMessage;
|
||||
return a.pubkey === login.publicKey
|
||||
? -1
|
||||
: b.pubkey === login.publicKey
|
||||
? 1
|
||||
: b.newestMessage - a.newestMessage;
|
||||
})
|
||||
.map(person)}
|
||||
</div>
|
||||
|
@ -1,32 +1,25 @@
|
||||
import { NostrPrefix } from "@snort/nostr";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
|
||||
import Spinner from "Icons/Spinner";
|
||||
import { setRelays } from "State/Login";
|
||||
import { parseNostrLink, profileLink, unixNowMs, unwrap } from "Util";
|
||||
import { parseNostrLink, profileLink } from "Util";
|
||||
import { getNip05PubKey } from "Pages/Login";
|
||||
import { System } from "System";
|
||||
|
||||
export default function NostrLinkHandler() {
|
||||
const params = useParams();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const link = decodeURIComponent(params["*"] ?? "").toLowerCase();
|
||||
|
||||
async function handleLink(link: string) {
|
||||
const nav = parseNostrLink(link);
|
||||
if (nav) {
|
||||
if ((nav.relays?.length ?? 0) > 0) {
|
||||
// todo: add as ephemerial connection
|
||||
dispatch(
|
||||
setRelays({
|
||||
relays: Object.fromEntries(unwrap(nav.relays).map(a => [a, { read: true, write: false }])),
|
||||
createdAt: unixNowMs(),
|
||||
})
|
||||
);
|
||||
nav.relays?.map(a => System.ConnectEphemeralRelay(a));
|
||||
}
|
||||
if (nav.type === NostrPrefix.Event || nav.type === NostrPrefix.Note || nav.type === NostrPrefix.Address) {
|
||||
navigate(`/e/${nav.encode()}`);
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { HexKey } from "@snort/nostr";
|
||||
import { markNotificationsRead } from "State/Login";
|
||||
import { RootState } from "State/Store";
|
||||
|
||||
import Timeline from "Element/Timeline";
|
||||
import { TaskList } from "Tasks/TaskList";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { markNotificationsRead } from "Login";
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const dispatch = useDispatch();
|
||||
const pubkey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const login = useLogin();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(markNotificationsRead());
|
||||
markNotificationsRead(login);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -19,12 +17,12 @@ export default function NotificationsPage() {
|
||||
<div className="main-content">
|
||||
<TaskList />
|
||||
</div>
|
||||
{pubkey && (
|
||||
{login.publicKey && (
|
||||
<Timeline
|
||||
subject={{
|
||||
type: "ptag",
|
||||
items: [pubkey],
|
||||
discriminator: pubkey.slice(0, 12),
|
||||
items: [login.publicKey],
|
||||
discriminator: login.publicKey.slice(0, 12),
|
||||
}}
|
||||
postsOnly={false}
|
||||
method={"TIME_RANGE"}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import "./ProfilePage.css";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { encodeTLV, EventKind, HexKey, NostrPrefix } from "@snort/nostr";
|
||||
|
||||
@ -36,7 +35,6 @@ import BlockList from "Element/BlockList";
|
||||
import MutedList from "Element/MutedList";
|
||||
import FollowsList from "Element/FollowListBase";
|
||||
import IconButton from "Element/IconButton";
|
||||
import { RootState } from "State/Store";
|
||||
import FollowsYou from "Element/FollowsYou";
|
||||
import QrCode from "Element/QrCode";
|
||||
import Modal from "Element/Modal";
|
||||
@ -46,6 +44,7 @@ import useHorizontalScroll from "Hooks/useHorizontalScroll";
|
||||
import { EmailRegex } from "Const";
|
||||
import { getNip05PubKey } from "Pages/Login";
|
||||
import { LNURL } from "LNURL";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -106,7 +105,7 @@ export default function ProfilePage() {
|
||||
const navigate = useNavigate();
|
||||
const [id, setId] = useState<string>();
|
||||
const user = useUserProfile(id);
|
||||
const loginPubKey = useSelector((s: RootState) => s.login.publicKey);
|
||||
const loginPubKey = useLogin().publicKey;
|
||||
const isMe = loginPubKey === id;
|
||||
const [showLnQr, setShowLnQr] = useState<boolean>(false);
|
||||
const [showProfileQr, setShowProfileQr] = useState<boolean>(false);
|
||||
|
@ -12,6 +12,7 @@ import { TimelineSubject } from "Feed/TimelineFeed";
|
||||
import { debounce, getRelayName, sha256, unixNow, unwrap } from "Util";
|
||||
|
||||
import messages from "./messages";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
interface RelayOption {
|
||||
url: string;
|
||||
@ -22,7 +23,7 @@ export default function RootPage() {
|
||||
const { formatMessage } = useIntl();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { publicKey: pubKey, tags, preferences } = useSelector((s: RootState) => s.login);
|
||||
const { publicKey: pubKey, tags, preferences } = useLogin();
|
||||
|
||||
const RootTab: Record<string, Tab> = {
|
||||
Posts: {
|
||||
@ -65,7 +66,7 @@ export default function RootPage() {
|
||||
}
|
||||
}, [location]);
|
||||
|
||||
const tagTabs = tags.map((t, idx) => {
|
||||
const tagTabs = tags.item.map((t, idx) => {
|
||||
return { text: `#${t}`, value: idx + 3, data: `/tag/${t}` };
|
||||
});
|
||||
const tabs = [RootTab.Posts, RootTab.PostsAndReplies, RootTab.Global, ...tagTabs];
|
||||
@ -81,8 +82,8 @@ export default function RootPage() {
|
||||
}
|
||||
|
||||
const FollowsHint = () => {
|
||||
const { publicKey: pubKey, follows } = useSelector((s: RootState) => s.login);
|
||||
if (follows?.length === 0 && pubKey) {
|
||||
const { publicKey: pubKey, follows } = useLogin();
|
||||
if (follows.item?.length === 0 && pubKey) {
|
||||
return (
|
||||
<FormattedMessage
|
||||
{...messages.NoFollows}
|
||||
@ -100,7 +101,7 @@ const FollowsHint = () => {
|
||||
};
|
||||
|
||||
const GlobalTab = () => {
|
||||
const { relays } = useSelector((s: RootState) => s.login);
|
||||
const { relays } = useLogin();
|
||||
const [relay, setRelay] = useState<RelayOption>();
|
||||
const [allRelays, setAllRelays] = useState<RelayOption[]>();
|
||||
const [now] = useState(unixNow());
|
||||
@ -177,8 +178,8 @@ const GlobalTab = () => {
|
||||
};
|
||||
|
||||
const PostsTab = () => {
|
||||
const follows = useSelector((s: RootState) => s.login.follows);
|
||||
const subject: TimelineSubject = { type: "pubkey", items: follows, discriminator: "follows" };
|
||||
const { follows } = useLogin();
|
||||
const subject: TimelineSubject = { type: "pubkey", items: follows.item, discriminator: "follows" };
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -189,8 +190,8 @@ const PostsTab = () => {
|
||||
};
|
||||
|
||||
const ConversationsTab = () => {
|
||||
const { follows } = useSelector((s: RootState) => s.login);
|
||||
const subject: TimelineSubject = { type: "pubkey", items: follows, discriminator: "follows" };
|
||||
const { follows } = useLogin();
|
||||
const subject: TimelineSubject = { type: "pubkey", items: follows.item, discriminator: "follows" };
|
||||
|
||||
return <Timeline subject={subject} postsOnly={false} method={"TIME_RANGE"} window={undefined} relay={undefined} />;
|
||||
};
|
||||
|
@ -1,24 +1,25 @@
|
||||
import { useMemo } from "react";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
|
||||
import { RecommendedFollows } from "Const";
|
||||
import Logo from "Element/Logo";
|
||||
import FollowListBase from "Element/FollowListBase";
|
||||
import { useMemo } from "react";
|
||||
import { clearEntropy } from "State/Login";
|
||||
import { clearEntropy } from "Login";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
export default function DiscoverFollows() {
|
||||
const { formatMessage } = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const login = useLogin();
|
||||
const navigate = useNavigate();
|
||||
const sortedReccomends = useMemo(() => {
|
||||
return RecommendedFollows.sort(() => (Math.random() >= 0.5 ? -1 : 1)).map(a => a.toLowerCase());
|
||||
}, []);
|
||||
|
||||
async function clearEntropyAndGo() {
|
||||
dispatch(clearEntropy());
|
||||
clearEntropy(login);
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,19 @@
|
||||
import { useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import Logo from "Element/Logo";
|
||||
import { services } from "Pages/Verification";
|
||||
import Nip5Service from "Element/Nip5Service";
|
||||
import ProfileImage from "Element/ProfileImage";
|
||||
import type { RootState } from "State/Store";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
export default function GetVerified() {
|
||||
const navigate = useNavigate();
|
||||
const { publicKey } = useSelector((s: RootState) => s.login);
|
||||
const { publicKey } = useLogin();
|
||||
const user = useUserProfile(publicKey);
|
||||
const [isVerified, setIsVerified] = useState(false);
|
||||
const name = user?.name || "nostrich";
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useIntl, FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@ -7,15 +6,15 @@ import { ApiHost } from "Const";
|
||||
import Logo from "Element/Logo";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import FollowListBase from "Element/FollowListBase";
|
||||
import { RootState } from "State/Store";
|
||||
import { bech32ToHex } from "Util";
|
||||
import SnortApi from "SnortApi";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
export default function ImportFollows() {
|
||||
const navigate = useNavigate();
|
||||
const currentFollows = useSelector((s: RootState) => s.login.follows);
|
||||
const currentFollows = useLogin().follows;
|
||||
const { formatMessage } = useIntl();
|
||||
const [twitterUsername, setTwitterUsername] = useState<string>("");
|
||||
const [follows, setFollows] = useState<string[]>([]);
|
||||
@ -23,7 +22,7 @@ export default function ImportFollows() {
|
||||
const api = new SnortApi(ApiHost);
|
||||
|
||||
const sortedTwitterFollows = useMemo(() => {
|
||||
return follows.map(a => bech32ToHex(a)).sort(a => (currentFollows.includes(a) ? 1 : -1));
|
||||
return follows.map(a => bech32ToHex(a)).sort(a => (currentFollows.item.includes(a) ? 1 : -1));
|
||||
}, [follows, currentFollows]);
|
||||
|
||||
async function loadFollows() {
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import Logo from "Element/Logo";
|
||||
import { CollapsedSection } from "Element/Collapsed";
|
||||
import Copy from "Element/Copy";
|
||||
import { RootState } from "State/Store";
|
||||
import { hexToBech32 } from "Util";
|
||||
import { hexToMnemonic } from "nip6";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
@ -69,7 +68,7 @@ const Extensions = () => {
|
||||
};
|
||||
|
||||
export default function NewUserFlow() {
|
||||
const { publicKey, privateKey, generatedEntropy } = useSelector((s: RootState) => s.login);
|
||||
const { publicKey, privateKey, generatedEntropy } = useLogin();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
|
@ -1,22 +1,20 @@
|
||||
import "./Index.css";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Icon from "Icons/Icon";
|
||||
import { logout } from "State/Login";
|
||||
import { logout } from "Login";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { unwrap } from "Util";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
const SettingsIndex = () => {
|
||||
const dispatch = useDispatch();
|
||||
const login = useLogin();
|
||||
const navigate = useNavigate();
|
||||
|
||||
function handleLogout() {
|
||||
dispatch(
|
||||
logout(() => {
|
||||
navigate("/");
|
||||
})
|
||||
);
|
||||
logout(unwrap(login.publicKey));
|
||||
navigate("/");
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,20 +1,20 @@
|
||||
import "./Preferences.css";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { FormattedMessage, useIntl } from "react-intl";
|
||||
import { Link } from "react-router-dom";
|
||||
import emoji from "@jukben/emoji-search";
|
||||
|
||||
import { DefaultImgProxy, setPreferences, UserPreferences } from "State/Login";
|
||||
import { RootState } from "State/Store";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { updatePreferences, UserPreferences } from "Login";
|
||||
import { DefaultImgProxy } from "Const";
|
||||
import { unwrap } from "Util";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
const PreferencesPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const { formatMessage } = useIntl();
|
||||
const perf = useSelector<RootState, UserPreferences>(s => s.login.preferences);
|
||||
const login = useLogin();
|
||||
const perf = login.preferences;
|
||||
|
||||
return (
|
||||
<div className="preferences">
|
||||
@ -32,12 +32,10 @@ const PreferencesPage = () => {
|
||||
<select
|
||||
value={perf.language}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
language: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
language: e.target.value,
|
||||
})
|
||||
}
|
||||
style={{ textTransform: "capitalize" }}>
|
||||
{["en", "ja", "es", "hu", "zh-CN", "zh-TW", "fr", "ar", "it", "id", "de", "ru", "sv", "hr"]
|
||||
@ -62,12 +60,10 @@ const PreferencesPage = () => {
|
||||
<select
|
||||
value={perf.theme}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
theme: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
theme: e.target.value,
|
||||
} as UserPreferences)
|
||||
}>
|
||||
<option value="system">
|
||||
<FormattedMessage {...messages.System} />
|
||||
@ -91,12 +87,10 @@ const PreferencesPage = () => {
|
||||
<select
|
||||
value={perf.defaultRootTab}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
defaultRootTab: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
defaultRootTab: e.target.value,
|
||||
} as UserPreferences)
|
||||
}>
|
||||
<option value="posts">
|
||||
<FormattedMessage {...messages.Posts} />
|
||||
@ -122,12 +116,10 @@ const PreferencesPage = () => {
|
||||
<select
|
||||
value={perf.autoLoadMedia}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
autoLoadMedia: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
autoLoadMedia: e.target.value,
|
||||
} as UserPreferences)
|
||||
}>
|
||||
<option value="none">
|
||||
<FormattedMessage {...messages.None} />
|
||||
@ -153,7 +145,7 @@ const PreferencesPage = () => {
|
||||
type="number"
|
||||
defaultValue={perf.defaultZapAmount}
|
||||
min={1}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, defaultZapAmount: parseInt(e.target.value || "0") }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, defaultZapAmount: parseInt(e.target.value || "0") })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -189,7 +181,7 @@ const PreferencesPage = () => {
|
||||
defaultValue={perf.fastZapDonate * 100}
|
||||
min={0}
|
||||
max={100}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, fastZapDonate: parseInt(e.target.value || "0") / 100 }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, fastZapDonate: parseInt(e.target.value || "0") / 100 })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -206,7 +198,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.autoZap}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, autoZap: e.target.checked }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, autoZap: e.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -225,12 +217,10 @@ const PreferencesPage = () => {
|
||||
type="checkbox"
|
||||
checked={perf.imgProxyConfig !== null}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
imgProxyConfig: e.target.checked ? DefaultImgProxy : null,
|
||||
})
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
imgProxyConfig: e.target.checked ? DefaultImgProxy : null,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@ -250,15 +240,13 @@ const PreferencesPage = () => {
|
||||
description: "Placeholder text for imgproxy url textbox",
|
||||
})}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
imgProxyConfig: {
|
||||
...unwrap(perf.imgProxyConfig),
|
||||
url: e.target.value,
|
||||
},
|
||||
})
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
imgProxyConfig: {
|
||||
...unwrap(perf.imgProxyConfig),
|
||||
url: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@ -276,15 +264,13 @@ const PreferencesPage = () => {
|
||||
description: "Hexidecimal 'key' input for improxy",
|
||||
})}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
imgProxyConfig: {
|
||||
...unwrap(perf.imgProxyConfig),
|
||||
key: e.target.value,
|
||||
},
|
||||
})
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
imgProxyConfig: {
|
||||
...unwrap(perf.imgProxyConfig),
|
||||
key: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@ -302,15 +288,13 @@ const PreferencesPage = () => {
|
||||
description: "Hexidecimal 'salt' input for imgproxy",
|
||||
})}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
imgProxyConfig: {
|
||||
...unwrap(perf.imgProxyConfig),
|
||||
salt: e.target.value,
|
||||
},
|
||||
})
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
imgProxyConfig: {
|
||||
...unwrap(perf.imgProxyConfig),
|
||||
salt: e.target.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
@ -331,7 +315,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.enableReactions}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, enableReactions: e.target.checked }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, enableReactions: e.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -348,12 +332,10 @@ const PreferencesPage = () => {
|
||||
className="emoji-selector"
|
||||
value={perf.reactionEmoji}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
reactionEmoji: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
reactionEmoji: e.target.value,
|
||||
})
|
||||
}>
|
||||
<option value="+">
|
||||
+ <FormattedMessage {...messages.Default} />
|
||||
@ -382,7 +364,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.confirmReposts}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, confirmReposts: e.target.checked }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, confirmReposts: e.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -399,7 +381,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.autoShowLatest}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, autoShowLatest: e.target.checked }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, autoShowLatest: e.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -415,12 +397,10 @@ const PreferencesPage = () => {
|
||||
<select
|
||||
value={perf.fileUploader}
|
||||
onChange={e =>
|
||||
dispatch(
|
||||
setPreferences({
|
||||
...perf,
|
||||
fileUploader: e.target.value,
|
||||
} as UserPreferences)
|
||||
)
|
||||
updatePreferences(login, {
|
||||
...perf,
|
||||
fileUploader: e.target.value,
|
||||
} as UserPreferences)
|
||||
}>
|
||||
<option value="void.cat">
|
||||
void.cat <FormattedMessage {...messages.Default} />
|
||||
@ -444,7 +424,7 @@ const PreferencesPage = () => {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={perf.showDebugMenus}
|
||||
onChange={e => dispatch(setPreferences({ ...perf, showDebugMenus: e.target.checked }))}
|
||||
onChange={e => updatePreferences(login, { ...perf, showDebugMenus: e.target.checked })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,22 +2,21 @@ import "./Profile.css";
|
||||
import Nostrich from "nostrich.webp";
|
||||
import { useEffect, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faShop } from "@fortawesome/free-solid-svg-icons";
|
||||
import { HexKey, TaggedRawEvent } from "@snort/nostr";
|
||||
import { TaggedRawEvent } from "@snort/nostr";
|
||||
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { useUserProfile } from "Hooks/useUserProfile";
|
||||
import { hexToBech32, openFile } from "Util";
|
||||
import Copy from "Element/Copy";
|
||||
import { RootState } from "State/Store";
|
||||
import useFileUpload from "Upload";
|
||||
|
||||
import messages from "./messages";
|
||||
import AsyncButton from "Element/AsyncButton";
|
||||
import { mapEventToProfile, UserCache } from "Cache";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
export interface ProfileSettingsProps {
|
||||
avatar?: boolean;
|
||||
@ -27,8 +26,7 @@ export interface ProfileSettingsProps {
|
||||
|
||||
export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||
const navigate = useNavigate();
|
||||
const id = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||
const privKey = useSelector<RootState, HexKey | undefined>(s => s.login.privateKey);
|
||||
const { publicKey: id, privateKey: privKey } = useLogin();
|
||||
const user = useUserProfile(id ?? "");
|
||||
const publisher = useEventPublisher();
|
||||
const uploader = useFileUpload();
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import ProfilePreview from "Element/ProfilePreview";
|
||||
import useRelayState from "Feed/RelayState";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { removeRelay } from "State/Login";
|
||||
import { parseId, unwrap } from "Util";
|
||||
import { System } from "System";
|
||||
import { removeRelay } from "Login";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
|
||||
import messages from "./messages";
|
||||
|
||||
const RelayInfo = () => {
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const login = useLogin();
|
||||
|
||||
const conn = Array.from(System.Sockets.values()).find(a => a.Id === params.id);
|
||||
const stats = useRelayState(conn?.Address ?? "");
|
||||
@ -105,7 +105,7 @@ const RelayInfo = () => {
|
||||
<div
|
||||
className="btn error"
|
||||
onClick={() => {
|
||||
dispatch(removeRelay(unwrap(conn).Address));
|
||||
removeRelay(login, unwrap(conn).Address);
|
||||
navigate("/settings/relays");
|
||||
}}>
|
||||
<FormattedMessage {...messages.Remove} />
|
||||
|
@ -1,24 +1,23 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import { randomSample } from "Util";
|
||||
import { randomSample, unixNowMs } from "Util";
|
||||
import Relay from "Element/Relay";
|
||||
import useEventPublisher from "Feed/EventPublisher";
|
||||
import { RootState } from "State/Store";
|
||||
import { setRelays } from "State/Login";
|
||||
import { System } from "System";
|
||||
|
||||
import messages from "./messages";
|
||||
import useLogin from "Hooks/useLogin";
|
||||
import { setRelays } from "Login";
|
||||
|
||||
const RelaySettingsPage = () => {
|
||||
const dispatch = useDispatch();
|
||||
const publisher = useEventPublisher();
|
||||
const relays = useSelector((s: RootState) => s.login.relays);
|
||||
const login = useLogin();
|
||||
const relays = login.relays;
|
||||
const [newRelay, setNewRelay] = useState<string>();
|
||||
|
||||
const otherConnections = useMemo(() => {
|
||||
return [...System.Sockets.keys()].filter(a => relays[a] === undefined);
|
||||
return [...System.Sockets.keys()].filter(a => relays.item[a] === undefined);
|
||||
}, [relays]);
|
||||
|
||||
async function saveRelays() {
|
||||
@ -69,13 +68,10 @@ const RelaySettingsPage = () => {
|
||||
if ((newRelay?.length ?? 0) > 0) {
|
||||
const parsed = new URL(newRelay ?? "");
|
||||
const payload = {
|
||||
relays: {
|
||||
...relays,
|
||||
[parsed.toString()]: { read: false, write: false },
|
||||
},
|
||||
createdAt: Math.floor(new Date().getTime() / 1000),
|
||||
...relays.item,
|
||||
[parsed.toString()]: { read: true, write: true },
|
||||
};
|
||||
dispatch(setRelays(payload));
|
||||
setRelays(login, payload, unixNowMs());
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,7 +81,7 @@ const RelaySettingsPage = () => {
|
||||
<FormattedMessage {...messages.Relays} />
|
||||
</h3>
|
||||
<div className="flex f-col mb10">
|
||||
{Object.keys(relays || {}).map(a => (
|
||||
{Object.keys(relays.item || {}).map(a => (
|
||||
<Relay addr={a} key={a} />
|
||||
))}
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user