readonly login sessions
This commit is contained in:
parent
3efb5321f6
commit
94da60ebfa
@ -16,7 +16,7 @@ interface BookmarksProps {
|
|||||||
|
|
||||||
const Bookmarks = ({ pubkey, bookmarks, related }: BookmarksProps) => {
|
const Bookmarks = ({ pubkey, bookmarks, related }: BookmarksProps) => {
|
||||||
const [onlyPubkey, setOnlyPubkey] = useState<HexKey | "all">("all");
|
const [onlyPubkey, setOnlyPubkey] = useState<HexKey | "all">("all");
|
||||||
const loginPubKey = useLogin().publicKey;
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
const ps = useMemo(() => {
|
const ps = useMemo(() => {
|
||||||
return [...new Set(bookmarks.map(ev => ev.pubkey))];
|
return [...new Set(bookmarks.map(ev => ev.pubkey))];
|
||||||
}, [bookmarks]);
|
}, [bookmarks]);
|
||||||
@ -47,7 +47,7 @@ const Bookmarks = ({ pubkey, bookmarks, related }: BookmarksProps) => {
|
|||||||
key={n.id}
|
key={n.id}
|
||||||
data={n}
|
data={n}
|
||||||
related={related}
|
related={related}
|
||||||
options={{ showTime: false, showBookmarked: true, canUnbookmark: loginPubKey === pubkey }}
|
options={{ showTime: false, showBookmarked: true, canUnbookmark: publicKey === pubkey }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@ -17,8 +17,8 @@ interface Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function CashuNuts({ token }: { token: string }) {
|
export default function CashuNuts({ token }: { token: string }) {
|
||||||
const login = useLogin();
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
const profile = useUserProfile(login.publicKey);
|
const profile = useUserProfile(publicKey);
|
||||||
|
|
||||||
async function copyToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
async function copyToken(e: React.MouseEvent<HTMLButtonElement>, token: string) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -18,14 +18,14 @@ export interface DMProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DM(props: DMProps) {
|
export default function DM(props: DMProps) {
|
||||||
const pubKey = useLogin().publicKey;
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const msg = props.data;
|
const msg = props.data;
|
||||||
const [content, setContent] = useState<string>();
|
const [content, setContent] = useState<string>();
|
||||||
const { ref, inView } = useInView({ triggerOnce: true });
|
const { ref, inView } = useInView({ triggerOnce: true });
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const isMe = msg.from === pubKey;
|
const isMe = msg.from === publicKey;
|
||||||
const otherPubkey = isMe ? pubKey : msg.from;
|
const otherPubkey = isMe ? publicKey : msg.from;
|
||||||
|
|
||||||
async function decrypt() {
|
async function decrypt() {
|
||||||
if (publisher) {
|
if (publisher) {
|
||||||
|
@ -10,12 +10,12 @@ import { Chat, ChatParticipant, createEmptyChatObject, useChatSystem } from "cha
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
export default function DmWindow({ id }: { id: string }) {
|
export default function DmWindow({ id }: { id: string }) {
|
||||||
const pubKey = useLogin().publicKey;
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
const dms = useChatSystem();
|
const dms = useChatSystem();
|
||||||
const chat = dms.find(a => a.id === id) ?? createEmptyChatObject(id);
|
const chat = dms.find(a => a.id === id) ?? createEmptyChatObject(id);
|
||||||
|
|
||||||
function participant(p: ChatParticipant) {
|
function participant(p: ChatParticipant) {
|
||||||
if (p.id === pubKey) {
|
if (p.id === publicKey) {
|
||||||
return <NoteToSelf className="f-grow mb-10" pubkey={p.id} />;
|
return <NoteToSelf className="f-grow mb-10" pubkey={p.id} />;
|
||||||
}
|
}
|
||||||
if (p.type === "pubkey") {
|
if (p.type === "pubkey") {
|
||||||
@ -56,7 +56,7 @@ export default function DmWindow({ id }: { id: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DmChatSelected({ chat }: { chat: Chat }) {
|
function DmChatSelected({ chat }: { chat: Chat }) {
|
||||||
const { publicKey: myPubKey } = useLogin();
|
const { publicKey: myPubKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
const sortedDms = useMemo(() => {
|
const sortedDms = useMemo(() => {
|
||||||
const myDms = chat?.messages;
|
const myDms = chat?.messages;
|
||||||
if (myPubKey && myDms) {
|
if (myPubKey && myDms) {
|
||||||
|
@ -18,9 +18,9 @@ export interface FollowButtonProps {
|
|||||||
export default function FollowButton(props: FollowButtonProps) {
|
export default function FollowButton(props: FollowButtonProps) {
|
||||||
const pubkey = parseId(props.pubkey);
|
const pubkey = parseId(props.pubkey);
|
||||||
const publisher = useEventPublisher();
|
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 isFollowing = follows.item.includes(pubkey);
|
||||||
const baseClassname = `${props.className} follow-button`;
|
const baseClassname = `${props.className ? ` ${props.className}` : ""}follow-button`;
|
||||||
|
|
||||||
async function follow(pubkey: HexKey) {
|
async function follow(pubkey: HexKey) {
|
||||||
if (publisher) {
|
if (publisher) {
|
||||||
@ -43,6 +43,7 @@ export default function FollowButton(props: FollowButtonProps) {
|
|||||||
return (
|
return (
|
||||||
<AsyncButton
|
<AsyncButton
|
||||||
className={isFollowing ? `${baseClassname} secondary` : baseClassname}
|
className={isFollowing ? `${baseClassname} secondary` : baseClassname}
|
||||||
|
disabled={readonly}
|
||||||
onClick={() => (isFollowing ? unfollow(pubkey) : follow(pubkey))}>
|
onClick={() => (isFollowing ? unfollow(pubkey) : follow(pubkey))}>
|
||||||
{isFollowing ? <FormattedMessage {...messages.Unfollow} /> : <FormattedMessage {...messages.Follow} />}
|
{isFollowing ? <FormattedMessage {...messages.Unfollow} /> : <FormattedMessage {...messages.Follow} />}
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
|
@ -51,7 +51,7 @@ export default function FollowListBase({
|
|||||||
<div className="flex mt10 mb10">
|
<div className="flex mt10 mb10">
|
||||||
<div className="f-grow bold">{title}</div>
|
<div className="f-grow bold">{title}</div>
|
||||||
{actions}
|
{actions}
|
||||||
<AsyncButton className="transparent" type="button" onClick={() => followAll()}>
|
<AsyncButton className="transparent" type="button" onClick={() => followAll()} disabled={login.readonly}>
|
||||||
<FormattedMessage {...messages.FollowAll} />
|
<FormattedMessage {...messages.FollowAll} />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,7 @@ import Icon from "Icons/Icon";
|
|||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
export function FollowingMark({ pubkey }: { pubkey: string }) {
|
export function FollowingMark({ pubkey }: { pubkey: string }) {
|
||||||
const { follows } = useLogin();
|
const { follows } = useLogin(s => ({ follows: s.follows }));
|
||||||
const doesFollow = follows.item.includes(pubkey);
|
const doesFollow = follows.item.includes(pubkey);
|
||||||
if (!doesFollow) return;
|
if (!doesFollow) return;
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import messages from "./messages";
|
|||||||
|
|
||||||
export default function LogoutButton() {
|
export default function LogoutButton() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const login = useLogin();
|
const login = useLogin(s => ({ publicKey: s.publicKey, id: s.id }));
|
||||||
|
|
||||||
if (!login.publicKey) return;
|
if (!login.publicKey) return;
|
||||||
return (
|
return (
|
||||||
|
@ -4,8 +4,8 @@ import useLogin from "Hooks/useLogin";
|
|||||||
const MixCloudEmbed = ({ link }: { link: string }) => {
|
const MixCloudEmbed = ({ link }: { link: string }) => {
|
||||||
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2);
|
const feedPath = (MixCloudRegex.test(link) && RegExp.$1) + "%2F" + (MixCloudRegex.test(link) && RegExp.$2);
|
||||||
|
|
||||||
const lightTheme = useLogin().preferences.theme === "light";
|
const { theme } = useLogin(s => ({ theme: s.preferences.theme }));
|
||||||
const lightParams = lightTheme ? "light=1" : "light=0";
|
const lightParams = theme === "light" ? "light=1" : "light=0";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
|
@ -43,8 +43,8 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { helpText = true } = props;
|
const { helpText = true } = props;
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const pubkey = useLogin().publicKey;
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
const user = useUserProfile(pubkey);
|
const user = useUserProfile(publicKey);
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const svc = useMemo(() => new ServiceProvider(props.service), [props.service]);
|
const svc = useMemo(() => new ServiceProvider(props.service), [props.service]);
|
||||||
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
|
const [serviceConfig, setServiceConfig] = useState<ServiceConfig>();
|
||||||
@ -179,11 +179,11 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function startBuy(handle: string, domain: string) {
|
async function startBuy(handle: string, domain: string) {
|
||||||
if (!pubkey) {
|
if (!publicKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rsp = await svc.RegisterHandle(handle, domain, pubkey);
|
const rsp = await svc.RegisterHandle(handle, domain, publicKey);
|
||||||
if ("error" in rsp) {
|
if ("error" in rsp) {
|
||||||
setError(rsp);
|
setError(rsp);
|
||||||
} else {
|
} else {
|
||||||
@ -193,7 +193,7 @@ export default function Nip5Service(props: Nip05ServiceProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function claimForSubscription(handle: string, domain: string, sub: string) {
|
async function claimForSubscription(handle: string, domain: string, sub: string) {
|
||||||
if (!pubkey || !publisher) {
|
if (!publicKey || !publisher) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,6 @@ interface NosteContextMenuProps {
|
|||||||
export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const { pinned, bookmarked, publicKey, preferences: prefs } = login;
|
|
||||||
const { mute, block } = useModeration();
|
const { mute, block } = useModeration();
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const [showBroadcast, setShowBroadcast] = useState(false);
|
const [showBroadcast, setShowBroadcast] = useState(false);
|
||||||
@ -37,7 +36,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
const langNames = new Intl.DisplayNames([...window.navigator.languages], {
|
const langNames = new Intl.DisplayNames([...window.navigator.languages], {
|
||||||
type: "language",
|
type: "language",
|
||||||
});
|
});
|
||||||
const isMine = ev.pubkey === publicKey;
|
const isMine = ev.pubkey === login.publicKey;
|
||||||
|
|
||||||
async function deleteEvent() {
|
async function deleteEvent() {
|
||||||
if (window.confirm(formatMessage(messages.ConfirmDeletion, { id: ev.id.substring(0, 8) })) && publisher) {
|
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) {
|
async function pin(id: HexKey) {
|
||||||
if (publisher) {
|
if (publisher) {
|
||||||
const es = [...pinned.item, id];
|
const es = [...login.pinned.item, id];
|
||||||
const ev = await publisher.noteList(es, Lists.Pinned);
|
const ev = await publisher.noteList(es, Lists.Pinned);
|
||||||
System.BroadcastEvent(ev);
|
System.BroadcastEvent(ev);
|
||||||
setPinned(login, es, ev.created_at * 1000);
|
setPinned(login, es, ev.created_at * 1000);
|
||||||
@ -98,7 +97,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
|
|
||||||
async function bookmark(id: HexKey) {
|
async function bookmark(id: HexKey) {
|
||||||
if (publisher) {
|
if (publisher) {
|
||||||
const es = [...bookmarked.item, id];
|
const es = [...login.bookmarked.item, id];
|
||||||
const ev = await publisher.noteList(es, Lists.Bookmarked);
|
const ev = await publisher.noteList(es, Lists.Bookmarked);
|
||||||
System.BroadcastEvent(ev);
|
System.BroadcastEvent(ev);
|
||||||
setBookmarked(login, es, ev.created_at * 1000);
|
setBookmarked(login, es, ev.created_at * 1000);
|
||||||
@ -131,13 +130,13 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
<Icon name="share" />
|
<Icon name="share" />
|
||||||
<FormattedMessage {...messages.Share} />
|
<FormattedMessage {...messages.Share} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{!pinned.item.includes(ev.id) && (
|
{!login.pinned.item.includes(ev.id) && !login.readonly && (
|
||||||
<MenuItem onClick={() => pin(ev.id)}>
|
<MenuItem onClick={() => pin(ev.id)}>
|
||||||
<Icon name="pin" />
|
<Icon name="pin" />
|
||||||
<FormattedMessage {...messages.Pin} />
|
<FormattedMessage {...messages.Pin} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{!bookmarked.item.includes(ev.id) && (
|
{!login.bookmarked.item.includes(ev.id) && !login.readonly && (
|
||||||
<MenuItem onClick={() => bookmark(ev.id)}>
|
<MenuItem onClick={() => bookmark(ev.id)}>
|
||||||
<Icon name="bookmark" />
|
<Icon name="bookmark" />
|
||||||
<FormattedMessage {...messages.Bookmark} />
|
<FormattedMessage {...messages.Bookmark} />
|
||||||
@ -147,23 +146,23 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
<Icon name="copy" />
|
<Icon name="copy" />
|
||||||
<FormattedMessage {...messages.CopyID} />
|
<FormattedMessage {...messages.CopyID} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => mute(ev.pubkey)}>
|
{!login.readonly && (
|
||||||
<Icon name="mute" />
|
<MenuItem onClick={() => mute(ev.pubkey)}>
|
||||||
<FormattedMessage {...messages.Mute} />
|
<Icon name="mute" />
|
||||||
</MenuItem>
|
<FormattedMessage {...messages.Mute} />
|
||||||
{prefs.enableReactions && (
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{login.preferences.enableReactions && !login.readonly && (
|
||||||
<MenuItem onClick={() => props.react("-")}>
|
<MenuItem onClick={() => props.react("-")}>
|
||||||
<Icon name="dislike" />
|
<Icon name="dislike" />
|
||||||
<FormattedMessage {...messages.DislikeAction} />
|
<FormattedMessage {...messages.DislikeAction} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{ev.pubkey === publicKey && (
|
<MenuItem onClick={handleReBroadcastButtonClick}>
|
||||||
<MenuItem onClick={handleReBroadcastButtonClick}>
|
<Icon name="relay" />
|
||||||
<Icon name="relay" />
|
<FormattedMessage defaultMessage="Broadcast Event" />
|
||||||
<FormattedMessage {...messages.ReBroadcast} />
|
</MenuItem>
|
||||||
</MenuItem>
|
{ev.pubkey !== login.publicKey && !login.readonly && (
|
||||||
)}
|
|
||||||
{ev.pubkey !== publicKey && (
|
|
||||||
<MenuItem onClick={() => block(ev.pubkey)}>
|
<MenuItem onClick={() => block(ev.pubkey)}>
|
||||||
<Icon name="block" />
|
<Icon name="block" />
|
||||||
<FormattedMessage {...messages.Block} />
|
<FormattedMessage {...messages.Block} />
|
||||||
@ -173,13 +172,13 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) {
|
|||||||
<Icon name="translate" />
|
<Icon name="translate" />
|
||||||
<FormattedMessage {...messages.TranslateTo} values={{ lang: langNames.of(lang.split("-")[0]) }} />
|
<FormattedMessage {...messages.TranslateTo} values={{ lang: langNames.of(lang.split("-")[0]) }} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{prefs.showDebugMenus && (
|
{login.preferences.showDebugMenus && (
|
||||||
<MenuItem onClick={() => copyEvent()}>
|
<MenuItem onClick={() => copyEvent()}>
|
||||||
<Icon name="json" />
|
<Icon name="json" />
|
||||||
<FormattedMessage {...messages.CopyJSON} />
|
<FormattedMessage {...messages.CopyJSON} />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{isMine && (
|
{isMine && !login.readonly && (
|
||||||
<MenuItem onClick={() => deleteEvent()}>
|
<MenuItem onClick={() => deleteEvent()}>
|
||||||
<Icon name="trash" className="red" />
|
<Icon name="trash" className="red" />
|
||||||
<FormattedMessage {...messages.Delete} />
|
<FormattedMessage {...messages.Delete} />
|
||||||
|
@ -32,7 +32,7 @@ export function NoteCreator() {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const uploader = useFileUpload();
|
const uploader = useFileUpload();
|
||||||
const login = useLogin();
|
const login = useLogin(s => ({ relays: s.relays, publicKey: s.publicKey }));
|
||||||
const note = useNoteCreator();
|
const note = useNoteCreator();
|
||||||
const relays = login.relays;
|
const relays = login.relays;
|
||||||
|
|
||||||
|
@ -47,8 +47,11 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
const { ev, positive, reposts, zaps } = props;
|
const { ev, positive, reposts, zaps } = props;
|
||||||
const system = useContext(SnortContext);
|
const system = useContext(SnortContext);
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const login = useLogin();
|
const {
|
||||||
const { publicKey, preferences: prefs } = login;
|
publicKey,
|
||||||
|
preferences: prefs,
|
||||||
|
readonly,
|
||||||
|
} = useLogin(s => ({ preferences: s.preferences, publicKey: s.publicKey, readonly: s.readonly }));
|
||||||
const author = useUserProfile(ev.pubkey);
|
const author = useUserProfile(ev.pubkey);
|
||||||
const interactionCache = useInteractionCache(publicKey, ev.id);
|
const interactionCache = useInteractionCache(publicKey, ev.id);
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
@ -59,6 +62,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
const walletState = useWallet();
|
const walletState = useWallet();
|
||||||
const wallet = walletState.wallet;
|
const wallet = walletState.wallet;
|
||||||
|
|
||||||
|
const canFastZap = wallet?.isReady() && !readonly;
|
||||||
const isMine = ev.pubkey === publicKey;
|
const isMine = ev.pubkey === publicKey;
|
||||||
const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
|
const zapTotal = zaps.reduce((acc, z) => acc + z.amount, 0);
|
||||||
const didZap = interactionCache.data.zapped || zaps.some(a => a.sender === publicKey);
|
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;
|
if (zapping || e?.isPropagationStopped()) return;
|
||||||
|
|
||||||
const lnurl = getZapTarget();
|
const lnurl = getZapTarget();
|
||||||
if (wallet?.isReady() && lnurl) {
|
if (canFastZap && lnurl) {
|
||||||
setZapping(true);
|
setZapping(true);
|
||||||
try {
|
try {
|
||||||
await fastZapInner(lnurl, prefs.defaultZapAmount);
|
await fastZapInner(lnurl, prefs.defaultZapAmount);
|
||||||
@ -194,7 +198,7 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
className={didZap ? "reacted" : ""}
|
className={didZap ? "reacted" : ""}
|
||||||
{...longPress()}
|
{...longPress()}
|
||||||
title={formatMessage({ defaultMessage: "Zap" })}
|
title={formatMessage({ defaultMessage: "Zap" })}
|
||||||
iconName={wallet?.isReady() ? "zapFast" : "zap"}
|
iconName={canFastZap ? "zapFast" : "zap"}
|
||||||
value={zapTotal}
|
value={zapTotal}
|
||||||
onClick={e => fastZap(e)}
|
onClick={e => fastZap(e)}
|
||||||
/>
|
/>
|
||||||
@ -204,13 +208,17 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function repostIcon() {
|
function repostIcon() {
|
||||||
|
if (readonly) return;
|
||||||
return (
|
return (
|
||||||
<AsyncFooterIcon
|
<AsyncFooterIcon
|
||||||
className={hasReposted() ? "reacted" : ""}
|
className={hasReposted() ? "reacted" : ""}
|
||||||
iconName="repeat"
|
iconName="repeat"
|
||||||
title={formatMessage({ defaultMessage: "Repost" })}
|
title={formatMessage({ defaultMessage: "Repost" })}
|
||||||
value={reposts.length}
|
value={reposts.length}
|
||||||
onClick={() => repost()}
|
onClick={async () => {
|
||||||
|
if (readonly) return;
|
||||||
|
await repost();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -226,12 +234,16 @@ export default function NoteFooter(props: NoteFooterProps) {
|
|||||||
iconName={reacted ? "heart-solid" : "heart"}
|
iconName={reacted ? "heart-solid" : "heart"}
|
||||||
title={formatMessage({ defaultMessage: "Like" })}
|
title={formatMessage({ defaultMessage: "Like" })}
|
||||||
value={positive.length}
|
value={positive.length}
|
||||||
onClick={() => react(prefs.reactionEmoji)}
|
onClick={async () => {
|
||||||
|
if (readonly) return;
|
||||||
|
await react(prefs.reactionEmoji);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function replyIcon() {
|
function replyIcon() {
|
||||||
|
if (readonly) return;
|
||||||
return (
|
return (
|
||||||
<AsyncFooterIcon
|
<AsyncFooterIcon
|
||||||
className={note.show ? "reacted" : ""}
|
className={note.show ? "reacted" : ""}
|
||||||
|
@ -96,6 +96,7 @@ export function LoginUnlock() {
|
|||||||
LoginStore.setPublisher(login.id, pub);
|
LoginStore.setPublisher(login.id, pub);
|
||||||
LoginStore.updateSession({
|
LoginStore.updateSession({
|
||||||
...login,
|
...login,
|
||||||
|
readonly: false,
|
||||||
privateKeyData: newPin,
|
privateKeyData: newPin,
|
||||||
privateKey: undefined,
|
privateKey: undefined,
|
||||||
});
|
});
|
||||||
@ -112,12 +113,20 @@ export function LoginUnlock() {
|
|||||||
LoginStore.setPublisher(login.id, pub);
|
LoginStore.setPublisher(login.id, pub);
|
||||||
LoginStore.updateSession({
|
LoginStore.updateSession({
|
||||||
...login,
|
...login,
|
||||||
|
readonly: false,
|
||||||
privateKeyData: key,
|
privateKeyData: key,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (login.publicKey && !publisher && sessionNeedsPin(login)) {
|
function makeSessionReadonly() {
|
||||||
|
LoginStore.updateSession({
|
||||||
|
...login,
|
||||||
|
readonly: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (login.publicKey && !publisher && sessionNeedsPin(login) && !login.readonly) {
|
||||||
if (login.privateKey !== undefined) {
|
if (login.privateKey !== undefined) {
|
||||||
return (
|
return (
|
||||||
<PinPrompt
|
<PinPrompt
|
||||||
@ -142,7 +151,7 @@ export function LoginUnlock() {
|
|||||||
}
|
}
|
||||||
onResult={unlockSession}
|
onResult={unlockSession}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
//nothing
|
makeSessionReadonly();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,45 +1,34 @@
|
|||||||
import { useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import { FormattedMessage } from "react-intl";
|
import { FormattedMessage } from "react-intl";
|
||||||
import { TaggedNostrEvent } from "@snort/system";
|
import { TaggedNostrEvent } from "@snort/system";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
|
||||||
import useEventPublisher from "Hooks/useEventPublisher";
|
|
||||||
import Modal from "Element/Modal";
|
import Modal from "Element/Modal";
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { System } from "index";
|
import AsyncButton from "./AsyncButton";
|
||||||
|
|
||||||
export function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: TaggedNostrEvent }) {
|
export function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: TaggedNostrEvent }) {
|
||||||
const [selected, setSelected] = useState<Array<string>>();
|
const [selected, setSelected] = useState<Array<string>>();
|
||||||
const publisher = useEventPublisher();
|
const system = useContext(SnortContext);
|
||||||
|
const { relays } = useLogin(s => ({ relays: s.relays }));
|
||||||
|
|
||||||
async function sendReBroadcast() {
|
async function sendReBroadcast() {
|
||||||
if (publisher) {
|
if (selected) {
|
||||||
if (selected) {
|
await Promise.all(selected.map(r => system.WriteOnceToRelay(r, ev)));
|
||||||
await Promise.all(selected.map(r => System.WriteOnceToRelay(r, ev)));
|
} else {
|
||||||
} else {
|
system.BroadcastEvent(ev);
|
||||||
System.BroadcastEvent(ev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit(ev: React.MouseEvent<HTMLButtonElement>) {
|
|
||||||
ev.stopPropagation();
|
|
||||||
sendReBroadcast().catch(console.warn);
|
|
||||||
}
|
|
||||||
|
|
||||||
const login = useLogin();
|
|
||||||
const relays = login.relays;
|
|
||||||
|
|
||||||
function renderRelayCustomisation() {
|
function renderRelayCustomisation() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex-column g8">
|
||||||
{Object.keys(relays.item || {})
|
{Object.keys(relays.item || {})
|
||||||
.filter(el => relays.item[el].write)
|
.filter(el => relays.item[el].write)
|
||||||
.map((r, i, a) => (
|
.map((r, i, a) => (
|
||||||
<div className="card flex">
|
<div className="card flex f-space">
|
||||||
<div className="flex f-col f-grow">
|
<div>{r}</div>
|
||||||
<div>{r}</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@ -63,13 +52,13 @@ export function ReBroadcaster({ onClose, ev }: { onClose: () => void; ev: Tagged
|
|||||||
<>
|
<>
|
||||||
<Modal id="broadcaster" className="note-creator-modal" onClose={onClose}>
|
<Modal id="broadcaster" className="note-creator-modal" onClose={onClose}>
|
||||||
{renderRelayCustomisation()}
|
{renderRelayCustomisation()}
|
||||||
<div className="note-creator-actions">
|
<div className="flex g8">
|
||||||
<button className="secondary" onClick={onClose}>
|
<button className="secondary" onClick={onClose}>
|
||||||
<FormattedMessage {...messages.Cancel} />
|
<FormattedMessage {...messages.Cancel} />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={onSubmit}>
|
<AsyncButton onClick={sendReBroadcast}>
|
||||||
<FormattedMessage {...messages.ReBroadcast} />
|
<FormattedMessage {...messages.ReBroadcast} />
|
||||||
</button>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
</>
|
</>
|
||||||
|
@ -248,9 +248,11 @@ function SendSatsInput(props: {
|
|||||||
onChange?: (v: SendSatsInputSelection) => void;
|
onChange?: (v: SendSatsInputSelection) => void;
|
||||||
onNextStage: (v: SendSatsInputSelection) => Promise<void>;
|
onNextStage: (v: SendSatsInputSelection) => Promise<void>;
|
||||||
}) {
|
}) {
|
||||||
const login = useLogin();
|
const { defaultZapAmount, readonly } = useLogin(s => ({
|
||||||
|
defaultZapAmount: s.preferences.defaultZapAmount,
|
||||||
|
readonly: s.readonly,
|
||||||
|
}));
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const defaultZapAmount = login.preferences.defaultZapAmount;
|
|
||||||
const amounts: Record<string, string> = {
|
const amounts: Record<string, string> = {
|
||||||
[defaultZapAmount.toString()]: "",
|
[defaultZapAmount.toString()]: "",
|
||||||
"1000": "👍",
|
"1000": "👍",
|
||||||
@ -264,7 +266,7 @@ function SendSatsInput(props: {
|
|||||||
const [comment, setComment] = useState<string>();
|
const [comment, setComment] = useState<string>();
|
||||||
const [amount, setAmount] = useState<number>(defaultZapAmount);
|
const [amount, setAmount] = useState<number>(defaultZapAmount);
|
||||||
const [customAmount, setCustomAmount] = useState<number>(defaultZapAmount);
|
const [customAmount, setCustomAmount] = useState<number>(defaultZapAmount);
|
||||||
const [zapType, setZapType] = useState(ZapType.PublicZap);
|
const [zapType, setZapType] = useState(readonly ? ZapType.AnonZap : ZapType.PublicZap);
|
||||||
|
|
||||||
function getValue() {
|
function getValue() {
|
||||||
return {
|
return {
|
||||||
@ -358,6 +360,7 @@ function SendSatsInput(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; setZapType: (t: ZapType) => void }) {
|
function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; setZapType: (t: ZapType) => void }) {
|
||||||
|
const { readonly } = useLogin(s => ({ readonly: s.readonly }));
|
||||||
const makeTab = (t: ZapType, n: React.ReactNode) => (
|
const makeTab = (t: ZapType, n: React.ReactNode) => (
|
||||||
<button type="button" className={zapType === t ? "" : "secondary"} onClick={() => setZapType(t)}>
|
<button type="button" className={zapType === t ? "" : "secondary"} onClick={() => setZapType(t)}>
|
||||||
{n}
|
{n}
|
||||||
@ -369,7 +372,7 @@ function SendSatsZapTypeSelector({ zapType, setZapType }: { zapType: ZapType; se
|
|||||||
<FormattedMessage defaultMessage="Zap Type" />
|
<FormattedMessage defaultMessage="Zap Type" />
|
||||||
</h3>
|
</h3>
|
||||||
<div className="flex g8">
|
<div className="flex g8">
|
||||||
{makeTab(ZapType.PublicZap, <FormattedMessage defaultMessage="Public" description="Public Zap" />)}
|
{!readonly && makeTab(ZapType.PublicZap, <FormattedMessage defaultMessage="Public" description="Public Zap" />)}
|
||||||
{/*makeTab(ZapType.PrivateZap, "Private")*/}
|
{/*makeTab(ZapType.PrivateZap, "Private")*/}
|
||||||
{makeTab(ZapType.AnonZap, <FormattedMessage defaultMessage="Anon" description="Anonymous Zap" />)}
|
{makeTab(ZapType.AnonZap, <FormattedMessage defaultMessage="Anon" description="Anonymous Zap" />)}
|
||||||
{makeTab(
|
{makeTab(
|
||||||
|
@ -1,9 +1,19 @@
|
|||||||
import { LoginStore } from "Login";
|
import { LoginSession, LoginStore } from "Login";
|
||||||
import { useSyncExternalStore } from "react";
|
import { useSyncExternalStore } from "react";
|
||||||
|
import { useSyncExternalStoreWithSelector } from "use-sync-external-store/with-selector";
|
||||||
|
|
||||||
export default function useLogin() {
|
export default function useLogin<T extends object = LoginSession>(selector?: (v: LoginSession) => T) {
|
||||||
return useSyncExternalStore(
|
if (selector) {
|
||||||
s => LoginStore.hook(s),
|
return useSyncExternalStoreWithSelector(
|
||||||
() => LoginStore.snapshot(),
|
s => LoginStore.hook(s),
|
||||||
);
|
() => LoginStore.snapshot(),
|
||||||
|
undefined,
|
||||||
|
selector,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return useSyncExternalStore<T>(
|
||||||
|
s => LoginStore.hook(s),
|
||||||
|
() => LoginStore.snapshot() as T,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ const getMessages = (locale: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const IntlProvider = ({ children }: { children: ReactNode }) => {
|
export const IntlProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const { language } = useLogin().preferences;
|
const { language } = useLogin(s => ({ language: s.preferences.language }));
|
||||||
const locale = language ?? getLocale();
|
const locale = language ?? getLocale();
|
||||||
const [messages, setMessages] = useState<Record<string, string>>(enMessages);
|
const [messages, setMessages] = useState<Record<string, string>>(enMessages);
|
||||||
|
|
||||||
|
@ -188,10 +188,8 @@ export function createPublisher(l: LoginSession, pin?: PinEncrypted) {
|
|||||||
case LoginSessionType.Nip7os: {
|
case LoginSessionType.Nip7os: {
|
||||||
return new EventPublisher(new Nip7OsSigner(), unwrap(l.publicKey));
|
return new EventPublisher(new Nip7OsSigner(), unwrap(l.publicKey));
|
||||||
}
|
}
|
||||||
default: {
|
case LoginSessionType.Nip7: {
|
||||||
if (l.publicKey) {
|
return new EventPublisher(new Nip7Signer(), unwrap(l.publicKey));
|
||||||
return new EventPublisher(new Nip7Signer(), l.publicKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,11 @@ export interface LoginSession {
|
|||||||
*/
|
*/
|
||||||
privateKey?: HexKey;
|
privateKey?: HexKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this session cannot sign events
|
||||||
|
*/
|
||||||
|
readonly: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypted private key
|
* Encrypted private key
|
||||||
*/
|
*/
|
||||||
|
@ -14,6 +14,7 @@ const LoggedOut = {
|
|||||||
id: "default",
|
id: "default",
|
||||||
type: "public_key",
|
type: "public_key",
|
||||||
preferences: DefaultPreferences,
|
preferences: DefaultPreferences,
|
||||||
|
readonly: true,
|
||||||
tags: {
|
tags: {
|
||||||
item: [],
|
item: [],
|
||||||
timestamp: 0,
|
timestamp: 0,
|
||||||
@ -65,6 +66,12 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
if (!this.#activeAccount) {
|
if (!this.#activeAccount) {
|
||||||
this.#activeAccount = this.#accounts.keys().next().value;
|
this.#activeAccount = this.#accounts.keys().next().value;
|
||||||
}
|
}
|
||||||
|
// reset readonly on load
|
||||||
|
for (const [, v] of this.#accounts) {
|
||||||
|
if (v.type === LoginSessionType.PrivateKey && v.readonly) {
|
||||||
|
v.readonly = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSessions() {
|
getSessions() {
|
||||||
@ -108,6 +115,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
const newSession = {
|
const newSession = {
|
||||||
...LoggedOut,
|
...LoggedOut,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
|
readonly: type === LoginSessionType.PublicKey,
|
||||||
type,
|
type,
|
||||||
publicKey: key,
|
publicKey: key,
|
||||||
relays: {
|
relays: {
|
||||||
@ -146,6 +154,7 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
...LoggedOut,
|
...LoggedOut,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
type: LoginSessionType.PrivateKey,
|
type: LoginSessionType.PrivateKey,
|
||||||
|
readonly: false,
|
||||||
privateKeyData: key,
|
privateKeyData: key,
|
||||||
publicKey: pubKey,
|
publicKey: pubKey,
|
||||||
generatedEntropy: entropy,
|
generatedEntropy: entropy,
|
||||||
@ -221,8 +230,21 @@ export class MultiAccountStore extends ExternalStore<LoginSession> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mark readonly
|
||||||
|
for (const [, v] of this.#accounts) {
|
||||||
|
if (v.type === LoginSessionType.PublicKey && !v.readonly) {
|
||||||
|
v.readonly = true;
|
||||||
|
didMigrate = true;
|
||||||
|
}
|
||||||
|
// reset readonly on load
|
||||||
|
if (v.type === LoginSessionType.PrivateKey && v.readonly) {
|
||||||
|
v.readonly = false;
|
||||||
|
didMigrate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (didMigrate) {
|
if (didMigrate) {
|
||||||
console.debug("Finished migration to MultiAccountStore");
|
console.debug("Finished migration in MultiAccountStore");
|
||||||
this.#save();
|
this.#save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,13 +66,14 @@ export default function Layout() {
|
|||||||
|
|
||||||
const NoteCreatorButton = () => {
|
const NoteCreatorButton = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const { readonly } = useLogin(s => ({ readonly: s.readonly }));
|
||||||
const { show, replyTo, update } = useNoteCreator(v => ({ show: v.show, replyTo: v.replyTo, update: v.update }));
|
const { show, replyTo, update } = useNoteCreator(v => ({ show: v.show, replyTo: v.replyTo, update: v.update }));
|
||||||
|
|
||||||
const shouldHideNoteCreator = useMemo(() => {
|
const shouldHideNoteCreator = useMemo(() => {
|
||||||
const isReplyNoteCreatorShowing = replyTo && show;
|
const isReplyNoteCreatorShowing = replyTo && show;
|
||||||
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
|
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
|
||||||
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
|
return readonly || isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
|
||||||
}, [location]);
|
}, [location, readonly]);
|
||||||
|
|
||||||
if (shouldHideNoteCreator) return;
|
if (shouldHideNoteCreator) return;
|
||||||
return (
|
return (
|
||||||
@ -96,7 +97,12 @@ const AccountHeader = () => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
const { publicKey, latestNotification, readNotifications } = useLogin();
|
const { publicKey, latestNotification, readNotifications, readonly } = useLogin(s => ({
|
||||||
|
publicKey: s.publicKey,
|
||||||
|
latestNotification: s.latestNotification,
|
||||||
|
readNotifications: s.readNotifications,
|
||||||
|
readonly: s.readonly,
|
||||||
|
}));
|
||||||
const profile = useUserProfile(publicKey);
|
const profile = useUserProfile(publicKey);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [searching, setSearching] = useState(false);
|
const [searching, setSearching] = useState(false);
|
||||||
@ -174,10 +180,12 @@ const AccountHeader = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Link className="btn" to="/messages">
|
{!readonly && (
|
||||||
<Icon name="mail" size={24} />
|
<Link className="btn" to="/messages">
|
||||||
{unreadDms > 0 && <span className="has-unread"></span>}
|
<Icon name="mail" size={24} />
|
||||||
</Link>
|
{unreadDms > 0 && <span className="has-unread"></span>}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<Link className="btn" to="/notifications" onClick={goToNotifications}>
|
<Link className="btn" to="/notifications" onClick={goToNotifications}>
|
||||||
<Icon name="bell-02" size={24} />
|
<Icon name="bell-02" size={24} />
|
||||||
{hasNotifications && <span className="has-unread"></span>}
|
{hasNotifications && <span className="has-unread"></span>}
|
||||||
|
@ -97,6 +97,7 @@ export default function LoginPage() {
|
|||||||
async function doLogin(pin?: string) {
|
async function doLogin(pin?: string) {
|
||||||
try {
|
try {
|
||||||
await loginHandler.doLogin(key, pin);
|
await loginHandler.doLogin(key, pin);
|
||||||
|
navigate("/");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof PinRequiredError) {
|
if (e instanceof PinRequiredError) {
|
||||||
setPin(true);
|
setPin(true);
|
||||||
|
@ -466,7 +466,7 @@ export default function ProfilePage() {
|
|||||||
<Icon name="zap" size={16} />
|
<Icon name="zap" size={16} />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
{loginPubKey && (
|
{loginPubKey && !login.readonly && (
|
||||||
<>
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
@ -22,7 +22,7 @@ export interface ProfileSettingsProps {
|
|||||||
|
|
||||||
export default function ProfileSettings(props: ProfileSettingsProps) {
|
export default function ProfileSettings(props: ProfileSettingsProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { publicKey: id } = useLogin();
|
const { publicKey: id, readonly } = useLogin(s => ({ publicKey: s.publicKey, readonly: s.readonly }));
|
||||||
const user = useUserProfile(id ?? "");
|
const user = useUserProfile(id ?? "");
|
||||||
const publisher = useEventPublisher();
|
const publisher = useEventPublisher();
|
||||||
const uploader = useFileUpload();
|
const uploader = useFileUpload();
|
||||||
@ -113,26 +113,48 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
|||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage defaultMessage="Name" />
|
<FormattedMessage defaultMessage="Name" />
|
||||||
</h4>
|
</h4>
|
||||||
<input className="w-max" type="text" value={name} onChange={e => setName(e.target.value)} />
|
<input
|
||||||
|
className="w-max"
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={e => setName(e.target.value)}
|
||||||
|
disabled={readonly}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-col w-max g8">
|
<div className="flex f-col w-max g8">
|
||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage defaultMessage="About" />
|
<FormattedMessage defaultMessage="About" />
|
||||||
</h4>
|
</h4>
|
||||||
<textarea className="w-max" onChange={e => setAbout(e.target.value)} value={about}></textarea>
|
<textarea
|
||||||
|
className="w-max"
|
||||||
|
onChange={e => setAbout(e.target.value)}
|
||||||
|
value={about}
|
||||||
|
disabled={readonly}></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-col w-max g8">
|
<div className="flex f-col w-max g8">
|
||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage defaultMessage="Website" />
|
<FormattedMessage defaultMessage="Website" />
|
||||||
</h4>
|
</h4>
|
||||||
<input className="w-max" type="text" value={website} onChange={e => setWebsite(e.target.value)} />
|
<input
|
||||||
|
className="w-max"
|
||||||
|
type="text"
|
||||||
|
value={website}
|
||||||
|
onChange={e => setWebsite(e.target.value)}
|
||||||
|
disabled={readonly}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex f-col w-max g8">
|
<div className="flex f-col w-max g8">
|
||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage defaultMessage="Nostr Address" />
|
<FormattedMessage defaultMessage="Nostr Address" />
|
||||||
</h4>
|
</h4>
|
||||||
<div className="flex f-col g8 w-max">
|
<div className="flex f-col g8 w-max">
|
||||||
<input type="text" className="w-max" value={nip05} onChange={e => setNip05(e.target.value)} />
|
<input
|
||||||
|
type="text"
|
||||||
|
className="w-max"
|
||||||
|
value={nip05}
|
||||||
|
onChange={e => setNip05(e.target.value)}
|
||||||
|
disabled={readonly}
|
||||||
|
/>
|
||||||
<small>
|
<small>
|
||||||
<FormattedMessage defaultMessage="Usernames are not unique on Nostr. The nostr address is your unique human-readable address that is unique to you upon registration." />
|
<FormattedMessage defaultMessage="Usernames are not unique on Nostr. The nostr address is your unique human-readable address that is unique to you upon registration." />
|
||||||
</small>
|
</small>
|
||||||
@ -150,9 +172,15 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
|||||||
<h4>
|
<h4>
|
||||||
<FormattedMessage defaultMessage="Lightning Address" />
|
<FormattedMessage defaultMessage="Lightning Address" />
|
||||||
</h4>
|
</h4>
|
||||||
<input className="w-max" type="text" value={lud16} onChange={e => setLud16(e.target.value)} />
|
<input
|
||||||
|
className="w-max"
|
||||||
|
type="text"
|
||||||
|
value={lud16}
|
||||||
|
onChange={e => setLud16(e.target.value)}
|
||||||
|
disabled={readonly}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<AsyncButton className="primary" onClick={() => saveProfile()}>
|
<AsyncButton className="primary" onClick={() => saveProfile()} disabled={readonly}>
|
||||||
<FormattedMessage defaultMessage="Save" />
|
<FormattedMessage defaultMessage="Save" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
@ -170,7 +198,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
|||||||
background: (banner?.length ?? 0) > 0 ? `no-repeat center/cover url("${banner}")` : undefined,
|
background: (banner?.length ?? 0) > 0 ? `no-repeat center/cover url("${banner}")` : undefined,
|
||||||
}}
|
}}
|
||||||
className="banner">
|
className="banner">
|
||||||
<AsyncButton type="button" onClick={() => setNewBanner()}>
|
<AsyncButton type="button" onClick={() => setNewBanner()} disabled={readonly}>
|
||||||
<FormattedMessage defaultMessage="Upload" />
|
<FormattedMessage defaultMessage="Upload" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
@ -178,7 +206,7 @@ export default function ProfileSettings(props: ProfileSettingsProps) {
|
|||||||
{(props.avatar ?? true) && (
|
{(props.avatar ?? true) && (
|
||||||
<div className="avatar-stack">
|
<div className="avatar-stack">
|
||||||
<Avatar pubkey={id} user={user} image={picture} />
|
<Avatar pubkey={id} user={user} image={picture} />
|
||||||
<AsyncButton type="button" className="btn-rnd" onClick={() => setNewAvatar()}>
|
<AsyncButton type="button" className="btn-rnd" onClick={() => setNewAvatar()} disabled={readonly}>
|
||||||
<Icon name="upload-01" />
|
<Icon name="upload-01" />
|
||||||
</AsyncButton>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,6 +8,7 @@ import useEventPublisher from "Hooks/useEventPublisher";
|
|||||||
import { System } from "index";
|
import { System } from "index";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { setRelays } from "Login";
|
import { setRelays } from "Login";
|
||||||
|
import AsyncButton from "Element/AsyncButton";
|
||||||
|
|
||||||
import messages from "./messages";
|
import messages from "./messages";
|
||||||
|
|
||||||
@ -91,9 +92,9 @@ const RelaySettingsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex mt10">
|
<div className="flex mt10">
|
||||||
<div className="f-grow"></div>
|
<div className="f-grow"></div>
|
||||||
<button type="button" onClick={() => saveRelays()}>
|
<AsyncButton type="button" onClick={() => saveRelays()} disabled={login.readonly}>
|
||||||
<FormattedMessage {...messages.Save} />
|
<FormattedMessage {...messages.Save} />
|
||||||
</button>
|
</AsyncButton>
|
||||||
</div>
|
</div>
|
||||||
{addRelay()}
|
{addRelay()}
|
||||||
<h3>
|
<h3>
|
||||||
|
@ -158,7 +158,7 @@ export function createEmptyChatObject(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useNip4Chat() {
|
export function useNip4Chat() {
|
||||||
const { publicKey } = useLogin();
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
return useSyncExternalStore(
|
return useSyncExternalStore(
|
||||||
c => Nip4Chats.hook(c),
|
c => Nip4Chats.hook(c),
|
||||||
() => Nip4Chats.snapshot(publicKey),
|
() => Nip4Chats.snapshot(publicKey),
|
||||||
@ -173,7 +173,7 @@ export function useNip29Chat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useNip24Chat() {
|
export function useNip24Chat() {
|
||||||
const { publicKey } = useLogin();
|
const { publicKey } = useLogin(s => ({ publicKey: s.publicKey }));
|
||||||
return useSyncExternalStore(
|
return useSyncExternalStore(
|
||||||
c => Nip24Chats.hook(c),
|
c => Nip24Chats.hook(c),
|
||||||
() => Nip24Chats.snapshot(publicKey),
|
() => Nip24Chats.snapshot(publicKey),
|
||||||
|
@ -458,6 +458,9 @@
|
|||||||
"GspYR7": {
|
"GspYR7": {
|
||||||
"defaultMessage": "{n} Dislike"
|
"defaultMessage": "{n} Dislike"
|
||||||
},
|
},
|
||||||
|
"Gxcr08": {
|
||||||
|
"defaultMessage": "Broadcast Event"
|
||||||
|
},
|
||||||
"H+vHiz": {
|
"H+vHiz": {
|
||||||
"defaultMessage": "Hex Key..",
|
"defaultMessage": "Hex Key..",
|
||||||
"description": "Hexidecimal 'key' input for improxy"
|
"description": "Hexidecimal 'key' input for improxy"
|
||||||
@ -564,6 +567,9 @@
|
|||||||
"LF5kYT": {
|
"LF5kYT": {
|
||||||
"defaultMessage": "Other Connections"
|
"defaultMessage": "Other Connections"
|
||||||
},
|
},
|
||||||
|
"LR1XjT": {
|
||||||
|
"defaultMessage": "Pin too short"
|
||||||
|
},
|
||||||
"LXxsbk": {
|
"LXxsbk": {
|
||||||
"defaultMessage": "Anonymous"
|
"defaultMessage": "Anonymous"
|
||||||
},
|
},
|
||||||
@ -1344,6 +1350,9 @@
|
|||||||
"defaultMessage": "Your key",
|
"defaultMessage": "Your key",
|
||||||
"description": "Label for key input"
|
"description": "Label for key input"
|
||||||
},
|
},
|
||||||
|
"wSZR47": {
|
||||||
|
"defaultMessage": "Submit"
|
||||||
|
},
|
||||||
"wWLwvh": {
|
"wWLwvh": {
|
||||||
"defaultMessage": "Anon",
|
"defaultMessage": "Anon",
|
||||||
"description": "Anonymous Zap"
|
"description": "Anonymous Zap"
|
||||||
|
@ -150,6 +150,7 @@
|
|||||||
"GUlSVG": "Claim your included Snort nostr address",
|
"GUlSVG": "Claim your included Snort nostr address",
|
||||||
"Gcn9NQ": "Magnet Link",
|
"Gcn9NQ": "Magnet Link",
|
||||||
"GspYR7": "{n} Dislike",
|
"GspYR7": "{n} Dislike",
|
||||||
|
"Gxcr08": "Broadcast Event",
|
||||||
"H+vHiz": "Hex Key..",
|
"H+vHiz": "Hex Key..",
|
||||||
"H0JBH6": "Log Out",
|
"H0JBH6": "Log Out",
|
||||||
"H6/kLh": "Order Paid!",
|
"H6/kLh": "Order Paid!",
|
||||||
@ -185,6 +186,7 @@
|
|||||||
"KoFlZg": "Enter mint URL",
|
"KoFlZg": "Enter mint URL",
|
||||||
"KtsyO0": "Enter Pin",
|
"KtsyO0": "Enter Pin",
|
||||||
"LF5kYT": "Other Connections",
|
"LF5kYT": "Other Connections",
|
||||||
|
"LR1XjT": "Pin too short",
|
||||||
"LXxsbk": "Anonymous",
|
"LXxsbk": "Anonymous",
|
||||||
"LgbKvU": "Comment",
|
"LgbKvU": "Comment",
|
||||||
"Lu5/Bj": "Open on Zapstr",
|
"Lu5/Bj": "Open on Zapstr",
|
||||||
@ -440,6 +442,7 @@
|
|||||||
"vxwnbh": "Amount of work to apply to all published events",
|
"vxwnbh": "Amount of work to apply to all published events",
|
||||||
"wEQDC6": "Edit",
|
"wEQDC6": "Edit",
|
||||||
"wLtRCF": "Your key",
|
"wLtRCF": "Your key",
|
||||||
|
"wSZR47": "Submit",
|
||||||
"wWLwvh": "Anon",
|
"wWLwvh": "Anon",
|
||||||
"wYSD2L": "Nostr Adddress",
|
"wYSD2L": "Nostr Adddress",
|
||||||
"wih7iJ": "name is blocked",
|
"wih7iJ": "name is blocked",
|
||||||
|
@ -384,12 +384,12 @@ export class Connection extends ExternalStore<ConnectionStateSnapshot> {
|
|||||||
}
|
}
|
||||||
this.AwaitingAuth.set(challenge, true);
|
this.AwaitingAuth.set(challenge, true);
|
||||||
const authEvent = await this.Auth(challenge, this.Address);
|
const authEvent = await this.Auth(challenge, this.Address);
|
||||||
return new Promise(resolve => {
|
if (!authEvent) {
|
||||||
if (!authEvent) {
|
authCleanup();
|
||||||
authCleanup();
|
throw new Error("No auth event");
|
||||||
return Promise.reject("no event");
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
return await new Promise(resolve => {
|
||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
authCleanup();
|
authCleanup();
|
||||||
resolve();
|
resolve();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user