feat: new login page

This commit is contained in:
Kieran 2023-02-09 18:05:45 +00:00
parent 51e8db651c
commit ddb8c9b58f
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
3 changed files with 231 additions and 45 deletions

View File

@ -32,14 +32,28 @@ export default function Layout() {
const { loggedOut, publicKey, relays, latestNotification, readNotifications, dms, preferences, newUserKey } =
useSelector((s: RootState) => s.login);
const { isMuted } = useModeration();
const [pageClass, setPageClass] = useState("page");
const usingDb = useDb();
const pub = useEventPublisher();
useLoginFeed();
const shouldHideNoteCreator = useMemo(() => {
const hideNoteCreator = ["/settings", "/messages", "/new"];
return hideNoteCreator.some(a => location.pathname.startsWith(a));
const hideOn = ["/settings", "/messages", "/new", "/login"];
return hideOn.some(a => location.pathname.startsWith(a));
}, [location]);
const shouldHideHeader = useMemo(() => {
const hideOn = ["/login"];
return hideOn.some(a => location.pathname.startsWith(a));
}, [location]);
useEffect(() => {
if (location.pathname.startsWith("/login")) {
setPageClass("");
} else {
setPageClass("page");
}
}, [location]);
const hasNotifications = useMemo(
@ -197,21 +211,23 @@ export default function Layout() {
return null;
}
return (
<div className="page">
<header>
<div className="logo" onClick={() => navigate("/")}>
Snort
</div>
<div>
{publicKey ? (
accountHeader()
) : (
<button type="button" onClick={() => navigate("/login")}>
<FormattedMessage {...messages.Login} />
</button>
)}
</div>
</header>
<div className={pageClass}>
{!shouldHideHeader && (
<header>
<div className="logo" onClick={() => navigate("/")}>
Snort
</div>
<div>
{publicKey ? (
accountHeader()
) : (
<button type="button" onClick={() => navigate("/login")}>
<FormattedMessage {...messages.Login} />
</button>
)}
</div>
</header>
)}
<Outlet />
{!shouldHideNoteCreator && (

83
src/Pages/Login.css Normal file
View File

@ -0,0 +1,83 @@
.login {
width: 100vw;
height: 100vh;
overflow: hidden;
display: flex;
}
.login > div {
flex: 1;
min-width: 0;
margin: 24px;
}
.login .logo {
margin-top: 16px;
margin-bottom: 67px;
}
.login > div:nth-child(1) {
display: flex;
justify-content: center;
}
.login > div:nth-child(1) > .login-container {
width: 403px;
}
.login > div:nth-child(2) > div.artwork {
background-image: var(--img-src);
width: 100%;
height: 100%;
border-radius: 50px;
background-size: cover;
background-position-x: center;
display: flex;
align-items: flex-end;
}
.login > div:nth-child(2) > div.artwork > div {
margin-left: 25px;
margin-bottom: 25px;
padding: 4px 12px;
background-color: var(--modal-bg-color);
border-radius: 1em;
color: var(--gray-light);
font-size: 14px;
}
.login > div:nth-child(2) > div.artwork .zap-button {
display: inline-block;
color: inherit;
background-color: inherit;
}
@media (max-width: 1024px) {
.login > div:nth-child(2) {
display: none;
}
}
.login input {
background-color: var(--gray-secondary);
padding: 12px 16px;
font-size: 16px;
}
.login .login-note {
color: var(--gray-light);
font-size: 14px;
}
.login .login-actions {
margin-top: 24px;
}
.login .login-actions > button {
margin-right: 10px;
}
.login .login-or {
margin-top: 56px;
margin-bottom: 64px;
}

View File

@ -1,13 +1,43 @@
import { useEffect, useState } from "react";
import "./Login.css";
import { CSSProperties, useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import * as secp from "@noble/secp256k1";
import { FormattedMessage } from "react-intl";
import { RootState } from "State/Store";
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
import { DefaultRelays, EmailRegex } from "Const";
import { bech32ToHex } from "Util";
import { bech32ToHex, unwrap } from "Util";
import { HexKey } from "Nostr";
import ZapButton from "Element/ZapButton";
import useImgProxy from "Feed/ImgProxy";
interface ArtworkEntry {
name: string;
pubkey: HexKey;
link: string;
}
// todo: fill more
const Artwork: Array<ArtworkEntry> = [
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
link: "https://uploads-ssl.webflow.com/63c880de767a98b3372e30e7/63e18f17ca872f54623301c1_Pura%20Vida.png",
},
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
link: "https://uploads-ssl.webflow.com/63c880de767a98b3372e30e7/63c9ee726ea27a41a123f43f_NostroshiSakamoto.png",
},
{
name: "",
pubkey: bech32ToHex("npub1r0rs5q2gk0e3dk3nlc7gnu378ec6cnlenqp8a3cjhyzu6f8k5sgs4sq9ac"),
link: "https://uploads-ssl.webflow.com/63c880de767a98b3372e30e7/63c9536909132a76054a4f70_In%20the%20Beginning.png",
},
];
export default function LoginPage() {
const dispatch = useDispatch();
@ -15,6 +45,8 @@ export default function LoginPage() {
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
const [key, setKey] = useState("");
const [error, setError] = useState("");
const [art, setArt] = useState<ArtworkEntry>();
const { proxy } = useImgProxy();
useEffect(() => {
if (publicKey) {
@ -22,6 +54,11 @@ export default function LoginPage() {
}
}, [publicKey, navigate]);
useEffect(() => {
const ret = unwrap(Artwork.at(Artwork.length * Math.random()));
proxy(ret.link).then(a => setArt({ ...ret, link: a }));
}, []);
async function getNip05PubKey(addr: string) {
const [username, domain] = addr.split("@");
const rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
@ -94,38 +131,88 @@ export default function LoginPage() {
}
return (
<>
<h2>Other Login Methods</h2>
<div className="flex">
<button type="button" onClick={doNip07Login}>
Login with Extension (NIP-07)
</button>
</div>
</>
<button type="button" onClick={doNip07Login}>
Login with Extension (NIP-07)
</button>
);
}
return (
<div className="main-content">
<h1>Login</h1>
<div className="flex">
<input
type="text"
placeholder="nsec / npub / nip-05 / hex private key..."
className="f-grow"
onChange={e => setKey(e.target.value)}
/>
<div className="login">
<div>
<div className="login-container">
<div className="logo" onClick={() => navigate("/")}>
Snort
</div>
<h1>
<FormattedMessage defaultMessage="Login" description="Login header" />
</h1>
<p>
<FormattedMessage defaultMessage="Your key" description="Label for key input" />
</p>
<div className="flex">
<input
type="text"
placeholder="nsec / npub / nip-05 / hex private key..."
className="f-grow"
onChange={e => setKey(e.target.value)}
/>
</div>
{error.length > 0 ? <b className="error">{error}</b> : null}
<p className="login-note">
<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>
<a href="">
<FormattedMessage
defaultMessage="Why is there no password field?"
description="Link to why your private key is your password"
/>
</a>
<div className="login-actions">
<button type="button" onClick={doLogin}>
<FormattedMessage defaultMessage="Login" description="Login button" />
</button>
{altLogins()}
</div>
<div className="flex login-or">
<div className="login-note">
<FormattedMessage defaultMessage="OR" description="Seperator text for Login / Generate Key" />
</div>
<div className="divider w-max"></div>
</div>
<h1>
<FormattedMessage defaultMessage="Create an Account" description="Heading for generate key flow" />
</h1>
<p className="login-note">
<FormattedMessage
defaultMessage="Generate a public / private key pair. Do not share your private key with anyone, this acts as your password. Once lost, it cannot be “reset” or recovered. Keep safe!"
description="Note about key security before generating a new key"
/>
</p>
<div className="tabs">
<button type="button" onClick={() => makeRandomKey()}>
<FormattedMessage defaultMessage="Generate Key" description="Button: Generate a new key" />
</button>
</div>
</div>
</div>
{error.length > 0 ? <b className="error">{error}</b> : null}
<div className="tabs">
<button type="button" onClick={doLogin}>
Login
</button>
<button type="button" onClick={() => makeRandomKey()}>
Generate Key
</button>
<div>
<div className="artwork" style={{ ["--img-src"]: `url('${art?.link}')` } as CSSProperties}>
<div>
<FormattedMessage
defaultMessage="Art by {name}"
description="Artwork attribution label"
values={{
name: "Karnage",
}}
/>
<ZapButton pubkey={art?.pubkey ?? ""} />
</div>
</div>
</div>
{altLogins()}
</div>
);
}