diff --git a/packages/app/src/Element/Bookmarks.tsx b/packages/app/src/Element/Bookmarks.tsx index f41511ca..e4a954a1 100644 --- a/packages/app/src/Element/Bookmarks.tsx +++ b/packages/app/src/Element/Bookmarks.tsx @@ -16,7 +16,7 @@ interface BookmarksProps { const Bookmarks = ({ pubkey, bookmarks, related }: BookmarksProps) => { const [onlyPubkey, setOnlyPubkey] = useState("all"); - const loginPubKey = useLogin().publicKey; + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); const ps = useMemo(() => { return [...new Set(bookmarks.map(ev => ev.pubkey))]; }, [bookmarks]); @@ -47,7 +47,7 @@ const Bookmarks = ({ pubkey, bookmarks, related }: BookmarksProps) => { key={n.id} data={n} related={related} - options={{ showTime: false, showBookmarked: true, canUnbookmark: loginPubKey === pubkey }} + options={{ showTime: false, showBookmarked: true, canUnbookmark: publicKey === pubkey }} /> ); })} diff --git a/packages/app/src/Element/CashuNuts.tsx b/packages/app/src/Element/CashuNuts.tsx index 74be20d0..62eaa149 100644 --- a/packages/app/src/Element/CashuNuts.tsx +++ b/packages/app/src/Element/CashuNuts.tsx @@ -17,8 +17,8 @@ interface Token { } export default function CashuNuts({ token }: { token: string }) { - const login = useLogin(); - const profile = useUserProfile(login.publicKey); + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); + const profile = useUserProfile(publicKey); async function copyToken(e: React.MouseEvent, token: string) { e.stopPropagation(); diff --git a/packages/app/src/Element/DM.tsx b/packages/app/src/Element/DM.tsx index 13ce1bf0..b5296de3 100644 --- a/packages/app/src/Element/DM.tsx +++ b/packages/app/src/Element/DM.tsx @@ -18,14 +18,14 @@ export interface DMProps { } export default function DM(props: DMProps) { - const pubKey = useLogin().publicKey; + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); const publisher = useEventPublisher(); const msg = props.data; const [content, setContent] = useState(); const { ref, inView } = useInView({ triggerOnce: true }); const { formatMessage } = useIntl(); - const isMe = msg.from === pubKey; - const otherPubkey = isMe ? pubKey : msg.from; + const isMe = msg.from === publicKey; + const otherPubkey = isMe ? publicKey : msg.from; async function decrypt() { if (publisher) { diff --git a/packages/app/src/Element/DmWindow.tsx b/packages/app/src/Element/DmWindow.tsx index 5b270747..ef4715d2 100644 --- a/packages/app/src/Element/DmWindow.tsx +++ b/packages/app/src/Element/DmWindow.tsx @@ -10,12 +10,12 @@ import { Chat, ChatParticipant, createEmptyChatObject, useChatSystem } from "cha import { FormattedMessage } from "react-intl"; export default function DmWindow({ id }: { id: string }) { - const pubKey = useLogin().publicKey; + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); const dms = useChatSystem(); const chat = dms.find(a => a.id === id) ?? createEmptyChatObject(id); function participant(p: ChatParticipant) { - if (p.id === pubKey) { + if (p.id === publicKey) { return ; } if (p.type === "pubkey") { @@ -56,7 +56,7 @@ export default function DmWindow({ id }: { id: string }) { } function DmChatSelected({ chat }: { chat: Chat }) { - const { publicKey: myPubKey } = useLogin(); + const { publicKey: myPubKey } = useLogin(s => ({ publicKey: s.publicKey })); const sortedDms = useMemo(() => { const myDms = chat?.messages; if (myPubKey && myDms) { diff --git a/packages/app/src/Element/FollowButton.tsx b/packages/app/src/Element/FollowButton.tsx index b47f1445..258d693b 100644 --- a/packages/app/src/Element/FollowButton.tsx +++ b/packages/app/src/Element/FollowButton.tsx @@ -18,9 +18,9 @@ export interface FollowButtonProps { export default function FollowButton(props: FollowButtonProps) { const pubkey = parseId(props.pubkey); const publisher = useEventPublisher(); - const { follows, relays } = useLogin(); + const { follows, relays, readonly } = useLogin(s => ({ follows: s.follows, relays: s.relays, readonly: s.readonly })); const isFollowing = follows.item.includes(pubkey); - const baseClassname = `${props.className} follow-button`; + const baseClassname = `${props.className ? ` ${props.className}` : ""}follow-button`; async function follow(pubkey: HexKey) { if (publisher) { @@ -43,6 +43,7 @@ export default function FollowButton(props: FollowButtonProps) { return ( (isFollowing ? unfollow(pubkey) : follow(pubkey))}> {isFollowing ? : } diff --git a/packages/app/src/Element/FollowListBase.tsx b/packages/app/src/Element/FollowListBase.tsx index 5e9ae9d7..377bd207 100644 --- a/packages/app/src/Element/FollowListBase.tsx +++ b/packages/app/src/Element/FollowListBase.tsx @@ -51,7 +51,7 @@ export default function FollowListBase({
{title}
{actions} - followAll()}> + followAll()} disabled={login.readonly}>
diff --git a/packages/app/src/Element/Following.tsx b/packages/app/src/Element/Following.tsx index 98ff8cc2..2ce66c8c 100644 --- a/packages/app/src/Element/Following.tsx +++ b/packages/app/src/Element/Following.tsx @@ -4,7 +4,7 @@ import Icon from "Icons/Icon"; import { FormattedMessage } from "react-intl"; export function FollowingMark({ pubkey }: { pubkey: string }) { - const { follows } = useLogin(); + const { follows } = useLogin(s => ({ follows: s.follows })); const doesFollow = follows.item.includes(pubkey); if (!doesFollow) return; diff --git a/packages/app/src/Element/LogoutButton.tsx b/packages/app/src/Element/LogoutButton.tsx index cda67067..edd0144a 100644 --- a/packages/app/src/Element/LogoutButton.tsx +++ b/packages/app/src/Element/LogoutButton.tsx @@ -7,7 +7,7 @@ import messages from "./messages"; export default function LogoutButton() { const navigate = useNavigate(); - const login = useLogin(); + const login = useLogin(s => ({ publicKey: s.publicKey, id: s.id })); if (!login.publicKey) return; return ( diff --git a/packages/app/src/Element/MixCloudEmbed.tsx b/packages/app/src/Element/MixCloudEmbed.tsx index d3c5aa01..0a3cd3eb 100644 --- a/packages/app/src/Element/MixCloudEmbed.tsx +++ b/packages/app/src/Element/MixCloudEmbed.tsx @@ -4,8 +4,8 @@ import useLogin from "Hooks/useLogin"; const MixCloudEmbed = ({ link }: { link: string }) => { const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2); - const lightTheme = useLogin().preferences.theme === "light"; - const lightParams = lightTheme ? "light=1" : "light=0"; + const { theme } = useLogin(s => ({ theme: s.preferences.theme })); + const lightParams = theme === "light" ? "light=1" : "light=0"; return ( <>
diff --git a/packages/app/src/Element/Nip5Service.tsx b/packages/app/src/Element/Nip5Service.tsx index 15368f33..434dfa9a 100644 --- a/packages/app/src/Element/Nip5Service.tsx +++ b/packages/app/src/Element/Nip5Service.tsx @@ -43,8 +43,8 @@ export default function Nip5Service(props: Nip05ServiceProps) { const navigate = useNavigate(); const { helpText = true } = props; const { formatMessage } = useIntl(); - const pubkey = useLogin().publicKey; - const user = useUserProfile(pubkey); + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); + const user = useUserProfile(publicKey); const publisher = useEventPublisher(); const svc = useMemo(() => new ServiceProvider(props.service), [props.service]); const [serviceConfig, setServiceConfig] = useState(); @@ -179,11 +179,11 @@ export default function Nip5Service(props: Nip05ServiceProps) { } async function startBuy(handle: string, domain: string) { - if (!pubkey) { + if (!publicKey) { return; } - const rsp = await svc.RegisterHandle(handle, domain, pubkey); + const rsp = await svc.RegisterHandle(handle, domain, publicKey); if ("error" in rsp) { setError(rsp); } else { @@ -193,7 +193,7 @@ export default function Nip5Service(props: Nip05ServiceProps) { } async function claimForSubscription(handle: string, domain: string, sub: string) { - if (!pubkey || !publisher) { + if (!publicKey || !publisher) { return; } diff --git a/packages/app/src/Element/NoteContextMenu.tsx b/packages/app/src/Element/NoteContextMenu.tsx index b0c1bc99..a06f62a9 100644 --- a/packages/app/src/Element/NoteContextMenu.tsx +++ b/packages/app/src/Element/NoteContextMenu.tsx @@ -29,7 +29,6 @@ interface NosteContextMenuProps { export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { const { formatMessage } = useIntl(); const login = useLogin(); - const { pinned, bookmarked, publicKey, preferences: prefs } = login; const { mute, block } = useModeration(); const publisher = useEventPublisher(); const [showBroadcast, setShowBroadcast] = useState(false); @@ -37,7 +36,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { const langNames = new Intl.DisplayNames([...window.navigator.languages], { type: "language", }); - const isMine = ev.pubkey === publicKey; + const isMine = ev.pubkey === login.publicKey; async function deleteEvent() { if (window.confirm(formatMessage(messages.ConfirmDeletion, { id: ev.id.substring(0, 8) })) && publisher) { @@ -89,7 +88,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { async function pin(id: HexKey) { if (publisher) { - const es = [...pinned.item, id]; + const es = [...login.pinned.item, id]; const ev = await publisher.noteList(es, Lists.Pinned); System.BroadcastEvent(ev); setPinned(login, es, ev.created_at * 1000); @@ -98,7 +97,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { async function bookmark(id: HexKey) { if (publisher) { - const es = [...bookmarked.item, id]; + const es = [...login.bookmarked.item, id]; const ev = await publisher.noteList(es, Lists.Bookmarked); System.BroadcastEvent(ev); setBookmarked(login, es, ev.created_at * 1000); @@ -131,13 +130,13 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { - {!pinned.item.includes(ev.id) && ( + {!login.pinned.item.includes(ev.id) && !login.readonly && ( pin(ev.id)}> )} - {!bookmarked.item.includes(ev.id) && ( + {!login.bookmarked.item.includes(ev.id) && !login.readonly && ( bookmark(ev.id)}> @@ -147,23 +146,23 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { - mute(ev.pubkey)}> - - - - {prefs.enableReactions && ( + {!login.readonly && ( + mute(ev.pubkey)}> + + + + )} + {login.preferences.enableReactions && !login.readonly && ( props.react("-")}> )} - {ev.pubkey === publicKey && ( - - - - - )} - {ev.pubkey !== publicKey && ( + + + + + {ev.pubkey !== login.publicKey && !login.readonly && ( block(ev.pubkey)}> @@ -173,13 +172,13 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { - {prefs.showDebugMenus && ( + {login.preferences.showDebugMenus && ( copyEvent()}> )} - {isMine && ( + {isMine && !login.readonly && ( deleteEvent()}> diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index 849698e8..139b61ae 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -32,7 +32,7 @@ export function NoteCreator() { const { formatMessage } = useIntl(); const publisher = useEventPublisher(); const uploader = useFileUpload(); - const login = useLogin(); + const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey })); const note = useNoteCreator(); const relays = login.relays; diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index d1432a6c..79599887 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -47,8 +47,11 @@ export default function NoteFooter(props: NoteFooterProps) { const { ev, positive, reposts, zaps } = props; const system = useContext(SnortContext); const { formatMessage } = useIntl(); - const login = useLogin(); - const { publicKey, preferences: prefs } = login; + const { + publicKey, + preferences: prefs, + readonly, + } = useLogin(s => ({ preferences: s.preferences, publicKey: s.publicKey, readonly: s.readonly })); const author = useUserProfile(ev.pubkey); const interactionCache = useInteractionCache(publicKey, ev.id); const publisher = useEventPublisher(); @@ -59,6 +62,7 @@ export default function NoteFooter(props: NoteFooterProps) { const walletState = useWallet(); const wallet = walletState.wallet; + const canFastZap = wallet?.isReady() && !readonly; const isMine = ev.pubkey === publicKey; const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0); const didZap = interactionCache.data.zapped || zaps.some(a => a.sender === publicKey); @@ -127,7 +131,7 @@ export default function NoteFooter(props: NoteFooterProps) { if (zapping || e?.isPropagationStopped()) return; const lnurl = getZapTarget(); - if (wallet?.isReady() && lnurl) { + if (canFastZap && lnurl) { setZapping(true); try { await fastZapInner(lnurl, prefs.defaultZapAmount); @@ -194,7 +198,7 @@ export default function NoteFooter(props: NoteFooterProps) { className={didZap ? "reacted" : ""} {...longPress()} title={formatMessage({ defaultMessage: "Zap" })} - iconName={wallet?.isReady() ? "zapFast" : "zap"} + iconName={canFastZap ? "zapFast" : "zap"} value={zapTotal} onClick={e => fastZap(e)} /> @@ -204,13 +208,17 @@ export default function NoteFooter(props: NoteFooterProps) { } function repostIcon() { + if (readonly) return; return ( repost()} + onClick={async () => { + if (readonly) return; + await repost(); + }} /> ); } @@ -226,12 +234,16 @@ export default function NoteFooter(props: NoteFooterProps) { iconName={reacted ? "heart-solid" : "heart"} title={formatMessage({ defaultMessage: "Like" })} value={positive.length} - onClick={() => react(prefs.reactionEmoji)} + onClick={async () => { + if (readonly) return; + await react(prefs.reactionEmoji); + }} /> ); } function replyIcon() { + if (readonly) return; return ( { - //nothing + makeSessionReadonly(); }} /> ); diff --git a/packages/app/src/Element/ReBroadcaster.tsx b/packages/app/src/Element/ReBroadcaster.tsx index 277448af..48e3f436 100644 --- a/packages/app/src/Element/ReBroadcaster.tsx +++ b/packages/app/src/Element/ReBroadcaster.tsx @@ -1,45 +1,34 @@ -import { useState } from "react"; +import { useContext, useState } from "react"; import { FormattedMessage } from "react-intl"; import { TaggedNostrEvent } from "@snort/system"; +import { SnortContext } from "@snort/system-react"; -import useEventPublisher from "Hooks/useEventPublisher"; import Modal from "Element/Modal"; import messages from "./messages"; import useLogin from "Hooks/useLogin"; -import { System } from "index"; +import AsyncButton from "./AsyncButton"; export function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: TaggedNostrEvent }) { const [selected, setSelected] = useState>(); - const publisher = useEventPublisher(); + const system = useContext(SnortContext); + const { relays } = useLogin(s => ({ relays: s.relays })); async function sendReBroadcast() { - if (publisher) { - if (selected) { - await Promise.all(selected.map(r => System.WriteOnceToRelay(r, ev))); - } else { - System.BroadcastEvent(ev); - } + if (selected) { + await Promise.all(selected.map(r => system.WriteOnceToRelay(r, ev))); + } else { + system.BroadcastEvent(ev); } } - function onSubmit(ev: React.MouseEvent) { - ev.stopPropagation(); - sendReBroadcast().catch(console.warn); - } - - const login = useLogin(); - const relays = login.relays; - function renderRelayCustomisation() { return ( -
+
{Object.keys(relays.item || {}) .filter(el => relays.item[el].write) .map((r, i, a) => ( -
-
-
{r}
-
+
+
{r}
void; ev: Tagged <> {renderRelayCustomisation()} -
+
- +
diff --git a/packages/app/src/Element/SendSats.tsx b/packages/app/src/Element/SendSats.tsx index a09cb8dd..46d791fe 100644 --- a/packages/app/src/Element/SendSats.tsx +++ b/packages/app/src/Element/SendSats.tsx @@ -248,9 +248,11 @@ function SendSatsInput(props: { onChange?: (v: SendSatsInputSelection) => void; onNextStage: (v: SendSatsInputSelection) => Promise; }) { - const login = useLogin(); + const { defaultZapAmount, readonly } = useLogin(s => ({ + defaultZapAmount: s.preferences.defaultZapAmount, + readonly: s.readonly, + })); const { formatMessage } = useIntl(); - const defaultZapAmount = login.preferences.defaultZapAmount; const amounts: Record = { [defaultZapAmount.toString()]: "", "1000": "👍", @@ -264,7 +266,7 @@ function SendSatsInput(props: { const [comment, setComment] = useState(); const [amount, setAmount] = useState(defaultZapAmount); const [customAmount, setCustomAmount] = useState(defaultZapAmount); - const [zapType, setZapType] = useState(ZapType.PublicZap); + const [zapType, setZapType] = useState(readonly ? ZapType.AnonZap : ZapType.PublicZap); function getValue() { return { @@ -358,6 +360,7 @@ function SendSatsInput(props: { } function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; setZapType: (t: ZapType) => void }) { + const { readonly } = useLogin(s => ({ readonly: s.readonly })); const makeTab = (t: ZapType, n: React.ReactNode) => (

- +

- setWebsite(e.target.value)} /> + setWebsite(e.target.value)} + disabled={readonly} + />

- setNip05(e.target.value)} /> + setNip05(e.target.value)} + disabled={readonly} + /> @@ -150,9 +172,15 @@ export default function ProfileSettings(props: ProfileSettingsProps) {

- setLud16(e.target.value)} /> + setLud16(e.target.value)} + disabled={readonly} + />
- saveProfile()}> + saveProfile()} disabled={readonly}>
@@ -170,7 +198,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) { background: (banner?.length ?? 0) > 0 ? `no-repeat center/cover url("${banner}")` : undefined, }} className="banner"> - setNewBanner()}> + setNewBanner()} disabled={readonly}>
@@ -178,7 +206,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) { {(props.avatar ?? true) && (
- setNewAvatar()}> + setNewAvatar()} disabled={readonly}>
diff --git a/packages/app/src/Pages/settings/Relays.tsx b/packages/app/src/Pages/settings/Relays.tsx index c49491e6..12675895 100644 --- a/packages/app/src/Pages/settings/Relays.tsx +++ b/packages/app/src/Pages/settings/Relays.tsx @@ -8,6 +8,7 @@ import useEventPublisher from "Hooks/useEventPublisher"; import { System } from "index"; import useLogin from "Hooks/useLogin"; import { setRelays } from "Login"; +import AsyncButton from "Element/AsyncButton"; import messages from "./messages"; @@ -91,9 +92,9 @@ const RelaySettingsPage = () => {
- +
{addRelay()}

diff --git a/packages/app/src/chat/index.ts b/packages/app/src/chat/index.ts index d6b9326a..ada8472e 100644 --- a/packages/app/src/chat/index.ts +++ b/packages/app/src/chat/index.ts @@ -158,7 +158,7 @@ export function createEmptyChatObject(id: string) { } export function useNip4Chat() { - const { publicKey } = useLogin(); + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); return useSyncExternalStore( c => Nip4Chats.hook(c), () => Nip4Chats.snapshot(publicKey), @@ -173,7 +173,7 @@ export function useNip29Chat() { } export function useNip24Chat() { - const { publicKey } = useLogin(); + const { publicKey } = useLogin(s => ({ publicKey: s.publicKey })); return useSyncExternalStore( c => Nip24Chats.hook(c), () => Nip24Chats.snapshot(publicKey), diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index e0721c8b..a6c74a8c 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -458,6 +458,9 @@ "GspYR7": { "defaultMessage": "{n} Dislike" }, + "Gxcr08": { + "defaultMessage": "Broadcast Event" + }, "H+vHiz": { "defaultMessage": "Hex Key..", "description": "Hexidecimal 'key' input for improxy" @@ -564,6 +567,9 @@ "LF5kYT": { "defaultMessage": "Other Connections" }, + "LR1XjT": { + "defaultMessage": "Pin too short" + }, "LXxsbk": { "defaultMessage": "Anonymous" }, @@ -1344,6 +1350,9 @@ "defaultMessage": "Your key", "description": "Label for key input" }, + "wSZR47": { + "defaultMessage": "Submit" + }, "wWLwvh": { "defaultMessage": "Anon", "description": "Anonymous Zap" diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 90891315..3f0119b4 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -150,6 +150,7 @@ "GUlSVG": "Claim your included Snort nostr address", "Gcn9NQ": "Magnet Link", "GspYR7": "{n} Dislike", + "Gxcr08": "Broadcast Event", "H+vHiz": "Hex Key..", "H0JBH6": "Log Out", "H6/kLh": "Order Paid!", @@ -185,6 +186,7 @@ "KoFlZg": "Enter mint URL", "KtsyO0": "Enter Pin", "LF5kYT": "Other Connections", + "LR1XjT": "Pin too short", "LXxsbk": "Anonymous", "LgbKvU": "Comment", "Lu5/Bj": "Open on Zapstr", @@ -440,6 +442,7 @@ "vxwnbh": "Amount of work to apply to all published events", "wEQDC6": "Edit", "wLtRCF": "Your key", + "wSZR47": "Submit", "wWLwvh": "Anon", "wYSD2L": "Nostr Adddress", "wih7iJ": "name is blocked", diff --git a/packages/system/src/connection.ts b/packages/system/src/connection.ts index dc0f857a..d1f22b19 100644 --- a/packages/system/src/connection.ts +++ b/packages/system/src/connection.ts @@ -384,12 +384,12 @@ export class Connection extends ExternalStore { } this.AwaitingAuth.set(challenge, true); const authEvent = await this.Auth(challenge, this.Address); - return new Promise(resolve => { - if (!authEvent) { - authCleanup(); - return Promise.reject("no event"); - } + if (!authEvent) { + authCleanup(); + throw new Error("No auth event"); + } + return await new Promise(resolve => { const t = setTimeout(() => { authCleanup(); resolve();