snort/packages/app/src/Pages/Login.tsx

258 lines
7.8 KiB
TypeScript
Raw Normal View History

2023-02-09 18:05:45 +00:00
import "./Login.css";
import { CSSProperties, useEffect, useState } from "react";
2022-12-29 22:23:41 +00:00
import { useNavigate } from "react-router-dom";
2023-02-12 12:31:48 +00:00
import { useIntl, FormattedMessage } from "react-intl";
import { HexKey } from "@snort/nostr";
2022-12-29 22:23:41 +00:00
2023-03-25 22:55:34 +00:00
import { bech32ToHex, unwrap } from "Util";
2023-02-09 18:05:45 +00:00
import ZapButton from "Element/ZapButton";
import useImgProxy from "Hooks/useImgProxy";
2023-04-14 11:33:19 +00:00
import Icon from "Icons/Icon";
import useLogin from "Hooks/useLogin";
import { generateNewLogin, LoginStore } from "Login";
import AsyncButton from "Element/AsyncButton";
2023-04-19 12:10:41 +00:00
import useLoginHandler from "Hooks/useLoginHandler";
2023-02-12 12:31:48 +00:00
2023-02-09 18:05:45 +00:00
interface ArtworkEntry {
name: string;
pubkey: HexKey;
link: string;
}
2023-04-14 11:33:19 +00:00
const KarnageKey = bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac");
2023-02-09 18:05:45 +00:00
// todo: fill more
const Artwork: Array<ArtworkEntry> = [
{
name: "",
2023-04-14 11:33:19 +00:00
pubkey: KarnageKey,
2023-02-09 18:49:12 +00:00
link: "https://void.cat/d/VKhPayp9ekeXYZGzAL9CxP",
2023-02-09 18:05:45 +00:00
},
{
name: "",
2023-04-14 11:33:19 +00:00
pubkey: KarnageKey,
2023-02-09 18:49:12 +00:00
link: "https://void.cat/d/3H2h8xxc3aEN6EVeobd8tw",
2023-02-09 18:05:45 +00:00
},
{
name: "",
2023-04-14 11:33:19 +00:00
pubkey: KarnageKey,
2023-02-09 18:49:12 +00:00
link: "https://void.cat/d/7i9W9PXn3TV86C4RUefNC9",
2023-02-09 18:05:45 +00:00
},
2023-02-09 18:49:12 +00:00
{
name: "",
2023-04-14 11:33:19 +00:00
pubkey: KarnageKey,
2023-02-09 18:51:50 +00:00
link: "https://void.cat/d/KtoX4ei6RYHY7HESg3Ve3k",
},
2023-02-09 18:05:45 +00:00
];
2022-12-27 23:46:13 +00:00
2023-02-27 13:17:13 +00:00
export async function getNip05PubKey(addr: string): Promise<string> {
const [username, domain] = addr.split("@");
2023-03-31 09:51:50 +00:00
const rsp = await fetch(
`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username.toLocaleLowerCase())}`
);
2023-02-27 13:17:13 +00:00
if (rsp.ok) {
const data = await rsp.json();
2023-03-31 09:51:50 +00:00
const pKey = data.names[username.toLowerCase()];
2023-02-27 13:17:13 +00:00
if (pKey) {
return pKey;
}
}
throw new Error("User key not found");
}
2022-12-18 14:51:47 +00:00
export default function LoginPage() {
const navigate = useNavigate();
2023-04-14 11:33:19 +00:00
const login = useLogin();
const [key, setKey] = useState("");
const [error, setError] = useState("");
2023-02-09 18:05:45 +00:00
const [art, setArt] = useState<ArtworkEntry>();
2023-04-08 17:23:20 +00:00
const [isMasking, setMasking] = useState(true);
2023-02-12 12:31:48 +00:00
const { formatMessage } = useIntl();
const { proxy } = useImgProxy();
2023-04-19 12:10:41 +00:00
const loginHandler = useLoginHandler();
const hasNip7 = "nostr" in window;
const hasSubtleCrypto = window.crypto.subtle !== undefined;
2022-12-27 23:46:13 +00:00
useEffect(() => {
2023-04-14 11:33:19 +00:00
if (login.publicKey) {
navigate("/");
2023-01-02 23:36:30 +00:00
}
2023-04-14 11:33:19 +00:00
}, [login, navigate]);
2023-01-02 23:36:30 +00:00
2023-02-09 18:05:45 +00:00
useEffect(() => {
const ret = unwrap(Artwork.at(Artwork.length * Math.random()));
2023-04-18 11:47:01 +00:00
const url = proxy(ret.link);
setArt({ ...ret, link: url });
2023-02-09 18:05:45 +00:00
}, []);
async function doLogin() {
try {
2023-04-19 12:10:41 +00:00
await loginHandler.doLogin(key);
} catch (e) {
if (e instanceof Error) {
setError(e.message);
} else {
setError(
formatMessage({
defaultMessage: "Unknown login error",
})
);
}
console.error(e);
2022-12-27 23:46:13 +00:00
}
}
2022-12-27 23:46:13 +00:00
async function makeRandomKey() {
2023-04-14 15:02:15 +00:00
await generateNewLogin();
navigate("/new");
}
2023-01-01 10:44:38 +00:00
async function doNip07Login() {
2023-05-04 13:32:24 +00:00
const relays =
"getRelays" in unwrap(window.nostr) ? await unwrap(window.nostr?.getRelays).call(window.nostr) : undefined;
2023-04-17 20:07:54 +00:00
const pubKey = await unwrap(window.nostr).getPublicKey();
2023-04-14 11:33:19 +00:00
LoginStore.loginWithPubkey(pubKey, relays);
}
2022-12-29 10:51:32 +00:00
function altLogins() {
if (!hasNip7) {
return;
2022-12-29 10:51:32 +00:00
}
2022-12-18 14:51:47 +00:00
return (
2023-02-09 18:05:45 +00:00
<button type="button" onClick={doNip07Login}>
2023-02-10 10:40:16 +00:00
<FormattedMessage
defaultMessage="Login with Extension (NIP-07)"
description="Login button for NIP7 key manager extension"
/>
2023-02-09 18:05:45 +00:00
</button>
2022-12-18 14:51:47 +00:00
);
}
function installExtension() {
if (hasSubtleCrypto) return;
return (
<>
<div className="flex login-or">
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
<div className="divider w-max"></div>
</div>
<h1 dir="auto">
<FormattedMessage
defaultMessage="Install Extension"
description="Heading for install key manager extension"
/>
</h1>
<p>
<FormattedMessage defaultMessage="Key manager extensions are more secure and allow you to easily login to any Nostr client, here are some well known extensions:" />
</p>
<ul>
<li>
<a href="https://getalby.com/" target="_blank" rel="noreferrer">
Alby
</a>
</li>
<li>
<a
href="https://chrome.google.com/webstore/detail/nos2x/kpgefcfmnafjgpblomihpgmejjdanjjp"
target="_blank"
rel="noreferrer">
nos2x
</a>
</li>
</ul>
<p>
<FormattedMessage
defaultMessage="If you want to try out some others, check out {link} for more!"
values={{
link: <a href="https://github.com/aljazceru/awesome-nostr#browser-extensions">awesome-nostr</a>,
}}
/>
</p>
<p>
<FormattedMessage defaultMessage="Once you setup your key manager extension and generated a key, you can follow our new users flow to setup your profile and help you find some interesting people on Nostr to follow." />
</p>
{hasNip7 ? (
<div className="login-actions">
<button type="button" onClick={() => doNip07Login().then(() => navigate("/new/username"))}>
<FormattedMessage defaultMessage="Setup Profile" />
</button>
</div>
) : (
<b className="error">
<FormattedMessage defaultMessage="Hmm, can't find a key manager extension.. try reloading the page." />
</b>
)}
</>
);
}
return (
2023-02-09 18:05:45 +00:00
<div className="login">
<div>
<div className="login-container">
2023-04-18 12:17:50 +00:00
<h1 className="logo" onClick={() => navigate("/")}>
2023-02-09 18:05:45 +00:00
Snort
2023-04-18 12:17:50 +00:00
</h1>
2023-02-13 16:57:51 +00:00
<h1 dir="auto">
2023-02-09 18:05:45 +00:00
<FormattedMessage defaultMessage="Login" description="Login header" />
</h1>
2023-02-13 16:57:51 +00:00
<p dir="auto">
2023-02-09 18:05:45 +00:00
<FormattedMessage defaultMessage="Your key" description="Label for key input" />
</p>
2023-02-12 12:31:48 +00:00
<div className="flex">
2023-02-09 18:05:45 +00:00
<input
2023-02-13 16:57:51 +00:00
dir="auto"
2023-04-08 13:24:45 +00:00
type={isMasking ? "password" : "text"}
2023-04-19 12:10:41 +00:00
placeholder={formatMessage({
defaultMessage: "nsec, npub, nip-05, hex, mnemonic",
})}
2023-02-09 18:05:45 +00:00
className="f-grow"
onChange={e => setKey(e.target.value)}
/>
2023-04-08 13:24:45 +00:00
<Icon
name={isMasking ? "openeye" : "closedeye"}
size={30}
className="highlight btn-sm pointer"
2023-04-10 18:16:44 +00:00
onClick={() => setMasking(!isMasking)}
2023-04-08 13:24:45 +00:00
/>
2023-02-09 18:05:45 +00:00
</div>
{error.length > 0 ? <b className="error">{error}</b> : null}
2023-05-09 15:25:59 +00:00
<p>
2023-02-09 18:05:45 +00:00
<FormattedMessage
defaultMessage="Only the secret key can be used to publish (sign events), everything else logs you in read-only mode."
description="Explanation for public key only login is read-only"
/>
</p>
2023-02-13 16:57:51 +00:00
<div dir="auto" className="login-actions">
2023-02-09 18:05:45 +00:00
<button type="button" onClick={doLogin}>
<FormattedMessage defaultMessage="Login" description="Login button" />
</button>
2023-04-18 12:17:50 +00:00
<AsyncButton onClick={() => makeRandomKey()}>
<FormattedMessage defaultMessage="Create Account" />
</AsyncButton>
2023-02-09 18:05:45 +00:00
{altLogins()}
</div>
{installExtension()}
2023-02-09 18:05:45 +00:00
</div>
</div>
2023-02-09 18:05:45 +00:00
<div>
2023-02-12 12:31:48 +00:00
<div className="artwork" style={{ ["--img-src"]: `url('${art?.link}')` } as CSSProperties}>
<div className="attribution">
2023-02-09 18:05:45 +00:00
<FormattedMessage
defaultMessage="Art by {name}"
description="Artwork attribution label"
values={{
2023-02-12 12:31:48 +00:00
name: <span className="artist">Karnage</span>,
2023-02-09 18:05:45 +00:00
}}
/>
<ZapButton pubkey={art?.pubkey ?? ""} />
</div>
</div>
</div>
</div>
);
2023-01-25 18:08:53 +00:00
}