feat: new login page
This commit is contained in:
parent
51e8db651c
commit
ddb8c9b58f
@ -32,14 +32,28 @@ export default function Layout() {
|
|||||||
const { loggedOut, publicKey, relays, latestNotification, readNotifications, dms, preferences, newUserKey } =
|
const { loggedOut, publicKey, relays, latestNotification, readNotifications, dms, preferences, newUserKey } =
|
||||||
useSelector((s: RootState) => s.login);
|
useSelector((s: RootState) => s.login);
|
||||||
const { isMuted } = useModeration();
|
const { isMuted } = useModeration();
|
||||||
|
const [pageClass, setPageClass] = useState("page");
|
||||||
|
|
||||||
const usingDb = useDb();
|
const usingDb = useDb();
|
||||||
const pub = useEventPublisher();
|
const pub = useEventPublisher();
|
||||||
useLoginFeed();
|
useLoginFeed();
|
||||||
|
|
||||||
const shouldHideNoteCreator = useMemo(() => {
|
const shouldHideNoteCreator = useMemo(() => {
|
||||||
const hideNoteCreator = ["/settings", "/messages", "/new"];
|
const hideOn = ["/settings", "/messages", "/new", "/login"];
|
||||||
return hideNoteCreator.some(a => location.pathname.startsWith(a));
|
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]);
|
}, [location]);
|
||||||
|
|
||||||
const hasNotifications = useMemo(
|
const hasNotifications = useMemo(
|
||||||
@ -197,21 +211,23 @@ export default function Layout() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className={pageClass}>
|
||||||
<header>
|
{!shouldHideHeader && (
|
||||||
<div className="logo" onClick={() => navigate("/")}>
|
<header>
|
||||||
Snort
|
<div className="logo" onClick={() => navigate("/")}>
|
||||||
</div>
|
Snort
|
||||||
<div>
|
</div>
|
||||||
{publicKey ? (
|
<div>
|
||||||
accountHeader()
|
{publicKey ? (
|
||||||
) : (
|
accountHeader()
|
||||||
<button type="button" onClick={() => navigate("/login")}>
|
) : (
|
||||||
<FormattedMessage {...messages.Login} />
|
<button type="button" onClick={() => navigate("/login")}>
|
||||||
</button>
|
<FormattedMessage {...messages.Login} />
|
||||||
)}
|
</button>
|
||||||
</div>
|
)}
|
||||||
</header>
|
</div>
|
||||||
|
</header>
|
||||||
|
)}
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
||||||
{!shouldHideNoteCreator && (
|
{!shouldHideNoteCreator && (
|
||||||
|
83
src/Pages/Login.css
Normal file
83
src/Pages/Login.css
Normal 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;
|
||||||
|
}
|
@ -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 { useDispatch, useSelector } from "react-redux";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import * as secp from "@noble/secp256k1";
|
import * as secp from "@noble/secp256k1";
|
||||||
|
import { FormattedMessage } from "react-intl";
|
||||||
|
|
||||||
import { RootState } from "State/Store";
|
import { RootState } from "State/Store";
|
||||||
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
import { setPrivateKey, setPublicKey, setRelays, setGeneratedPrivateKey } from "State/Login";
|
||||||
import { DefaultRelays, EmailRegex } from "Const";
|
import { DefaultRelays, EmailRegex } from "Const";
|
||||||
import { bech32ToHex } from "Util";
|
import { bech32ToHex, unwrap } from "Util";
|
||||||
import { HexKey } from "Nostr";
|
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() {
|
export default function LoginPage() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -15,6 +45,8 @@ export default function LoginPage() {
|
|||||||
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
const publicKey = useSelector<RootState, HexKey | undefined>(s => s.login.publicKey);
|
||||||
const [key, setKey] = useState("");
|
const [key, setKey] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [art, setArt] = useState<ArtworkEntry>();
|
||||||
|
const { proxy } = useImgProxy();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (publicKey) {
|
if (publicKey) {
|
||||||
@ -22,6 +54,11 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
}, [publicKey, navigate]);
|
}, [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) {
|
async function getNip05PubKey(addr: string) {
|
||||||
const [username, domain] = addr.split("@");
|
const [username, domain] = addr.split("@");
|
||||||
const rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
|
const rsp = await fetch(`https://${domain}/.well-known/nostr.json?name=${encodeURIComponent(username)}`);
|
||||||
@ -94,38 +131,88 @@ export default function LoginPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<button type="button" onClick={doNip07Login}>
|
||||||
<h2>Other Login Methods</h2>
|
Login with Extension (NIP-07)
|
||||||
<div className="flex">
|
</button>
|
||||||
<button type="button" onClick={doNip07Login}>
|
|
||||||
Login with Extension (NIP-07)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-content">
|
<div className="login">
|
||||||
<h1>Login</h1>
|
<div>
|
||||||
<div className="flex">
|
<div className="login-container">
|
||||||
<input
|
<div className="logo" onClick={() => navigate("/")}>
|
||||||
type="text"
|
Snort
|
||||||
placeholder="nsec / npub / nip-05 / hex private key..."
|
</div>
|
||||||
className="f-grow"
|
<h1>
|
||||||
onChange={e => setKey(e.target.value)}
|
<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>
|
</div>
|
||||||
{error.length > 0 ? <b className="error">{error}</b> : null}
|
<div>
|
||||||
<div className="tabs">
|
<div className="artwork" style={{ ["--img-src"]: `url('${art?.link}')` } as CSSProperties}>
|
||||||
<button type="button" onClick={doLogin}>
|
<div>
|
||||||
Login
|
<FormattedMessage
|
||||||
</button>
|
defaultMessage="Art by {name}"
|
||||||
<button type="button" onClick={() => makeRandomKey()}>
|
description="Artwork attribution label"
|
||||||
Generate Key
|
values={{
|
||||||
</button>
|
name: "Karnage",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ZapButton pubkey={art?.pubkey ?? ""} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{altLogins()}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user