This commit is contained in:
2023-09-21 21:02:59 +01:00
parent 4b57d57f94
commit 71f7f728fd
69 changed files with 863 additions and 864 deletions

View File

@ -70,7 +70,7 @@ export function SnortDeckLayout() {
</div>
{deckScope.thread && (
<>
<Modal onClose={() => deckScope.setThread(undefined)} className="thread-overlay">
<Modal id="thread-overlay" onClose={() => deckScope.setThread(undefined)} className="thread-overlay">
<ThreadContextWrapper link={deckScope.thread}>
<SpotlightFromThread onClose={() => deckScope.setThread(undefined)} />
<div>

View File

@ -3,7 +3,7 @@ import { useParams } from "react-router-dom";
import { FormattedMessage } from "react-intl";
import Timeline from "Element/Timeline";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import useLogin from "Hooks/useLogin";
import { setTags } from "Login";
import { System } from "index";

View File

@ -1,6 +1,5 @@
import "./Layout.css";
import { useEffect, useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Link, Outlet, useLocation, useNavigate } from "react-router-dom";
import { FormattedMessage, useIntl } from "react-intl";
import { useUserProfile } from "@snort/system-react";
@ -9,8 +8,6 @@ import { NostrLink, NostrPrefix, tryParseNostrLink } from "@snort/system";
import messages from "./messages";
import Icon from "Icons/Icon";
import { RootState } from "State/Store";
import { setShow, reset } from "State/NoteCreator";
import useLoginFeed from "Feed/LoginFeed";
import { NoteCreator } from "Element/NoteCreator";
import { mapPlanName } from "./subscribe";
@ -23,29 +20,19 @@ import Spinner from "Icons/Spinner";
import { fetchNip05Pubkey } from "Nip05/Verifier";
import { useTheme } from "Hooks/useTheme";
import { useLoginRelays } from "Hooks/useLoginRelays";
import { useNoteCreator } from "State/NoteCreator";
import { LoginUnlock } from "Element/PinPrompt";
export default function Layout() {
const location = useLocation();
const replyTo = useSelector((s: RootState) => s.noteCreator.replyTo);
const isNoteCreatorShowing = useSelector((s: RootState) => s.noteCreator.show);
const isReplyNoteCreatorShowing = replyTo && isNoteCreatorShowing;
const dispatch = useDispatch();
const navigate = useNavigate();
const { publicKey, subscriptions } = useLogin();
const currentSubscription = getCurrentSubscription(subscriptions);
const note = useNoteCreator();
const isReplyNoteCreatorShowing = note.replyTo && note.show;
const [pageClass, setPageClass] = useState("page");
useLoginFeed();
useTheme();
useLoginRelays();
const handleNoteCreatorButtonClick = () => {
if (replyTo) {
dispatch(reset());
}
dispatch(setShow(true));
};
const shouldHideNoteCreator = useMemo(() => {
const hideOn = ["/settings", "/messages", "/new", "/login", "/donate", "/e", "/subscribe"];
return isReplyNoteCreatorShowing || hideOn.some(a => location.pathname.startsWith(a));
@ -66,34 +53,22 @@ export default function Layout() {
}
}, [location]);
return (
return (<>
<div className={pageClass}>
{!shouldHideHeader && (
<header className="main-content">
<Link to="/" className="logo">
<h1>Snort</h1>
{currentSubscription && (
<small className="flex">
<Icon name="diamond" size={10} className="mr5" />
{mapPlanName(currentSubscription.type)}
</small>
)}
</Link>
{publicKey ? (
<AccountHeader />
) : (
<button type="button" onClick={() => navigate("/login")}>
<FormattedMessage {...messages.Login} />
</button>
)}
<LogoHeader />
<AccountHeader />
</header>
)}
<Outlet />
{!shouldHideNoteCreator && (
<>
<button className="primary note-create-button" onClick={handleNoteCreatorButtonClick}>
<button className="primary note-create-button" onClick={() => note.update(v => {
v.replyTo = undefined;
v.show = true
})}>
<Icon name="plus" size={16} />
</button>
<NoteCreator />
@ -101,6 +76,8 @@ export default function Layout() {
)}
<Toaster />
</div>
<LoginUnlock />
</>
);
}
@ -156,6 +133,13 @@ const AccountHeader = () => {
}
}
if (!publicKey) {
return (
<button type="button" onClick={() => navigate("/login")}>
<FormattedMessage {...messages.Login} />
</button>
)
}
return (
<div className="header-actions">
{!location.pathname.startsWith("/search") && (
@ -199,3 +183,20 @@ const AccountHeader = () => {
</div>
);
};
function LogoHeader() {
const { subscriptions } = useLogin();
const currentSubscription = getCurrentSubscription(subscriptions);
return (
<Link to="/" className="logo">
<h1>Snort</h1>
{currentSubscription && (
<small className="flex">
<Icon name="diamond" size={10} className="mr5" />
{mapPlanName(currentSubscription.type)}
</small>
)}
</Link>
);
}

View File

@ -12,13 +12,14 @@ import Icon from "Icons/Icon";
import useLogin from "Hooks/useLogin";
import { generateNewLogin, LoginSessionType, LoginStore } from "Login";
import AsyncButton from "Element/AsyncButton";
import useLoginHandler from "Hooks/useLoginHandler";
import useLoginHandler, { PinRequiredError } from "Hooks/useLoginHandler";
import { secp256k1 } from "@noble/curves/secp256k1";
import { bytesToHex } from "@noble/curves/abstract/utils";
import Modal from "Element/Modal";
import QrCode from "Element/QrCode";
import Copy from "Element/Copy";
import { delay } from "SnortUtils";
import { PinPrompt } from "Element/PinPrompt";
declare global {
interface Window {
@ -78,6 +79,7 @@ export default function LoginPage() {
const login = useLogin();
const [key, setKey] = useState("");
const [error, setError] = useState("");
const [pin, setPin] = useState(false);
const [art, setArt] = useState<ArtworkEntry>();
const [isMasking, setMasking] = useState(true);
const { formatMessage } = useIntl();
@ -99,10 +101,13 @@ export default function LoginPage() {
setArt({ ...ret, link: url });
}, []);
async function doLogin() {
async function doLogin(pin?: string) {
try {
await loginHandler.doLogin(key);
await loginHandler.doLogin(key, pin);
} catch (e) {
if (e instanceof PinRequiredError) {
setPin(true);
}
if (e instanceof Error) {
setError(e.message);
} else {
@ -116,10 +121,16 @@ export default function LoginPage() {
}
}
async function makeRandomKey() {
await generateNewLogin();
window.plausible?.("Generate Account");
navigate("/new");
async function makeRandomKey(pin: string) {
try {
await generateNewLogin(pin);
window.plausible?.("Generate Account");
navigate("/new");
} catch (e) {
if (e instanceof Error) {
setError(e.message);
}
}
}
async function doNip07Login() {
@ -157,7 +168,7 @@ export default function LoginPage() {
<FormattedMessage defaultMessage="Nostr Connect (NIP-46)" description="Login button for NIP-46 signer app" />
</AsyncButton>
{nostrConnect && (
<Modal onClose={() => setNostrConnect("")}>
<Modal id="nostr-connect" onClose={() => setNostrConnect("")}>
<div className="flex f-col">
<QrCode data={nostrConnect} />
<Copy text={nostrConnect} />
@ -283,12 +294,19 @@ export default function LoginPage() {
/>
</p>
<div dir="auto" className="login-actions">
<AsyncButton type="button" onClick={doLogin}>
<AsyncButton type="button" onClick={() => doLogin()}>
<FormattedMessage defaultMessage="Login" description="Login button" />
</AsyncButton>
<AsyncButton onClick={() => makeRandomKey()}>
<AsyncButton onClick={() => setPin(true)}>
<FormattedMessage defaultMessage="Create Account" />
</AsyncButton>
{pin && <PinPrompt onResult={pin => {
if (key) {
doLogin(pin);
} else {
makeRandomKey(pin);
}
}} onCancel={() => setPin(false)} />}
{altLogins()}
</div>
{installExtension()}

View File

@ -210,7 +210,7 @@ function NewChatWindow() {
<Icon name="plus" size={16} />
</button>
{show && (
<Modal onClose={() => setShow(false)} className="new-chat-modal">
<Modal id="new-chat" onClose={() => setShow(false)} className="new-chat-modal">
<div className="flex-column g16">
<div className="flex f-space">
<h2>

View File

@ -326,14 +326,14 @@ export default function ProfilePage() {
targets={
lnurl?.lnurl && id
? [
{
type: "lnurl",
value: lnurl?.lnurl,
weight: 1,
name: user?.display_name || user?.name,
zap: { pubkey: id },
} as ZapTarget,
]
{
type: "lnurl",
value: lnurl?.lnurl,
weight: 1,
name: user?.display_name || user?.name,
zap: { pubkey: id },
} as ZapTarget,
]
: undefined
}
show={showLnQr}
@ -447,7 +447,7 @@ export default function ProfilePage() {
<Icon name="qr" size={16} />
</IconButton>
{showProfileQr && (
<Modal className="qr-modal" onClose={() => setShowProfileQr(false)}>
<Modal id="profile-qr" className="qr-modal" onClose={() => setShowProfileQr(false)}>
<ProfileImage pubkey={id} />
<QrCode data={link} className="m10 align-center" />
<Copy text={link} className="align-center" />

View File

@ -5,7 +5,7 @@ import { mapEventToProfile } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import Logo from "Element/Logo";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import useLogin from "Hooks/useLogin";
import { UserCache } from "Cache";
import AvatarEditor from "Element/AvatarEditor";

View File

@ -6,7 +6,7 @@ import { mapEventToProfile } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { System } from "index";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import { openFile } from "SnortUtils";
import useFileUpload from "Upload";
import AsyncButton from "Element/AsyncButton";

View File

@ -4,7 +4,7 @@ import { unixNowMs } from "@snort/shared";
import { randomSample } from "SnortUtils";
import Relay from "Element/Relay";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import { System } from "index";
import useLogin from "Hooks/useLogin";
import { setRelays } from "Login";

View File

@ -5,7 +5,6 @@ import { Outlet, useLocation, useNavigate } from "react-router-dom";
import Icon from "Icons/Icon";
import { LoginStore, logout } from "Login";
import useLogin from "Hooks/useLogin";
import { unwrap } from "SnortUtils";
import { getCurrentSubscription } from "Subscription";
import messages from "./messages";
@ -19,7 +18,7 @@ const SettingsIndex = () => {
const sub = getCurrentSubscription(LoginStore.allSubscriptions());
function handleLogout() {
logout(unwrap(login.publicKey));
logout(login.id);
navigate("/");
}

View File

@ -4,7 +4,7 @@ import { LNURL } from "@snort/shared";
import { ApiHost } from "Const";
import AsyncButton from "Element/AsyncButton";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import SnortServiceProvider, { ManageHandle } from "Nip05/SnortServiceProvider";
export default function LNForwardAddress({ handle }: { handle: ManageHandle }) {

View File

@ -3,7 +3,7 @@ import { FormattedMessage } from "react-intl";
import { Link, useNavigate } from "react-router-dom";
import { ApiHost } from "Const";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import SnortServiceProvider, { ManageHandle } from "Nip05/SnortServiceProvider";
export default function ListHandles() {

View File

@ -1,6 +1,6 @@
import { ApiHost } from "Const";
import AsyncButton from "Element/AsyncButton";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import { ServiceError } from "Nip05/ServiceProvider";
import SnortServiceProvider, { ManageHandle } from "Nip05/SnortServiceProvider";
import { useState } from "react";

View File

@ -3,7 +3,7 @@ import { FormattedMessage } from "react-intl";
import { Link, useNavigate } from "react-router-dom";
import PageSpinner from "Element/PageSpinner";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import SnortApi, { Subscription, SubscriptionError } from "SnortApi";
import { mapSubscriptionErrorCode } from ".";
import SubscriptionCard from "./SubscriptionCard";

View File

@ -5,7 +5,7 @@ import SnortApi, { Subscription, SubscriptionError } from "SnortApi";
import { mapPlanName, mapSubscriptionErrorCode } from ".";
import AsyncButton from "Element/AsyncButton";
import Icon from "Icons/Icon";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import SendSats from "Element/SendSats";
import Nip5Service from "Element/Nip5Service";
import { SnortNostrAddressService } from "Pages/NostrAddressPage";

View File

@ -8,7 +8,7 @@ import { formatShort } from "Number";
import { LockedFeatures, Plans, SubscriptionType } from "Subscription";
import ManageSubscriptionPage from "Pages/subscribe/ManageSubscription";
import AsyncButton from "Element/AsyncButton";
import useEventPublisher from "Feed/EventPublisher";
import useEventPublisher from "Hooks/useEventPublisher";
import SnortApi, { SubscriptionError, SubscriptionErrorCode } from "SnortApi";
import SendSats from "Element/SendSats";