208 lines
6.6 KiB
TypeScript
208 lines
6.6 KiB
TypeScript
import { unwrap } from "@snort/shared";
|
|
import { NotEncrypted } from "@snort/system";
|
|
import { SnortContext } from "@snort/system-react";
|
|
import classNames from "classnames";
|
|
import { FormEvent, useContext, useState } from "react";
|
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
import { Link, useNavigate } from "react-router-dom";
|
|
|
|
import AsyncButton from "@/Components/Button/AsyncButton";
|
|
import Icon from "@/Components/Icons/Icon";
|
|
import useLoginHandler from "@/Hooks/useLoginHandler";
|
|
import { trackEvent } from "@/Utils";
|
|
import { generateNewLogin, LoginSessionType, LoginStore } from "@/Utils/Login";
|
|
|
|
import { NewUserState } from ".";
|
|
|
|
const NSEC_NPUB_REGEX = /(nsec1|npub1)[a-zA-Z0-9]{20,65}/gi;
|
|
|
|
export function SignIn() {
|
|
const navigate = useNavigate();
|
|
const { formatMessage } = useIntl();
|
|
const [key, setKey] = useState("");
|
|
const [error, setError] = useState("");
|
|
const [useKey, setUseKey] = useState(false);
|
|
const loginHandler = useLoginHandler();
|
|
|
|
const hasNip7 = "nostr" in window;
|
|
async function doNip07Login() {
|
|
/*const relays =
|
|
"getRelays" in unwrap(window.nostr) ? await unwrap(window.nostr?.getRelays).call(window.nostr) : undefined;*/
|
|
const pubKey = await unwrap(window.nostr).getPublicKey();
|
|
LoginStore.loginWithPubkey(pubKey, LoginSessionType.Nip7);
|
|
trackEvent("Login", { type: "NIP7" });
|
|
navigate("/");
|
|
}
|
|
|
|
async function onSubmit(e) {
|
|
e.preventDefault();
|
|
doLogin(key);
|
|
}
|
|
|
|
async function doLogin(key: string) {
|
|
setError("");
|
|
try {
|
|
await loginHandler.doLogin(key, key => Promise.resolve(new NotEncrypted(key)));
|
|
trackEvent("Login", { type: "Key" });
|
|
navigate("/");
|
|
} catch (e) {
|
|
if (e instanceof Error) {
|
|
setError(e.message);
|
|
} else {
|
|
setError(
|
|
formatMessage({
|
|
defaultMessage: "Unknown login error",
|
|
id: "OLEm6z",
|
|
}),
|
|
);
|
|
}
|
|
console.error(e);
|
|
}
|
|
}
|
|
|
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const val = e.target.value;
|
|
if (val.match(NSEC_NPUB_REGEX)) {
|
|
doLogin(val);
|
|
} else {
|
|
setKey(val);
|
|
}
|
|
};
|
|
|
|
const nip7Login = hasNip7 && !useKey;
|
|
return (
|
|
<div className="flex flex-col g24">
|
|
<img src={CONFIG.icon} width={48} height={48} className="br mr-auto ml-auto" />
|
|
<div className="flex flex-col g16 items-center">
|
|
<h1>
|
|
<FormattedMessage defaultMessage="Sign In" />
|
|
</h1>
|
|
{nip7Login && <FormattedMessage defaultMessage="Use a nostr signer extension to sign in" />}
|
|
</div>
|
|
<div className={classNames("flex flex-col g16", { "items-center": nip7Login })}>
|
|
{hasNip7 && !useKey && (
|
|
<>
|
|
<AsyncButton onClick={doNip07Login}>
|
|
<div className="circle bg-warning p12 text-white">
|
|
<Icon name="key" />
|
|
</div>
|
|
<FormattedMessage defaultMessage="Sign in with Nostr Extension" />
|
|
</AsyncButton>
|
|
<Link to="" className="highlight">
|
|
<FormattedMessage defaultMessage="Supported Extensions" />
|
|
</Link>
|
|
<AsyncButton onClick={() => setUseKey(true)}>
|
|
<FormattedMessage defaultMessage="Sign in with key" />
|
|
</AsyncButton>
|
|
</>
|
|
)}
|
|
{(!hasNip7 || useKey) && (
|
|
<form onSubmit={onSubmit} className="flex flex-col gap-4">
|
|
<input
|
|
type="text"
|
|
placeholder={formatMessage({
|
|
defaultMessage: "nsec, npub, nip-05, hex, mnemonic",
|
|
id: "X7xU8J",
|
|
})}
|
|
value={key}
|
|
onChange={onChange}
|
|
className="new-username"
|
|
/>
|
|
{error && <b className="error">{error}</b>}
|
|
<div className="flex justify-center">
|
|
<AsyncButton onClick={onSubmit} className="primary">
|
|
<FormattedMessage defaultMessage="Login" />
|
|
</AsyncButton>
|
|
</div>
|
|
</form>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col g16 items-center">
|
|
<Link to={"/login/sign-up"}>
|
|
<FormattedMessage defaultMessage="Don't have an account?" />
|
|
</Link>
|
|
<AsyncButton className="secondary" onClick={() => navigate("/login/sign-up")}>
|
|
<FormattedMessage defaultMessage="Sign Up" />
|
|
</AsyncButton>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SignUp() {
|
|
const { formatMessage } = useIntl();
|
|
const navigate = useNavigate();
|
|
const [name, setName] = useState("");
|
|
const system = useContext(SnortContext);
|
|
|
|
const onSubmit = async (e: FormEvent) => {
|
|
e.preventDefault();
|
|
if (CONFIG.signUp.quickStart) {
|
|
return generateNewLogin(system, key => Promise.resolve(new NotEncrypted(key)), {
|
|
name,
|
|
}).then(() => {
|
|
trackEvent("Login", { newAccount: true });
|
|
navigate("/trending/notes");
|
|
});
|
|
}
|
|
navigate("/login/sign-up/profile", {
|
|
state: {
|
|
name: name,
|
|
} as NewUserState,
|
|
});
|
|
};
|
|
|
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const val = e.target.value;
|
|
if (val.match(NSEC_NPUB_REGEX)) {
|
|
e.preventDefault();
|
|
} else {
|
|
setName(val);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col g24">
|
|
<img src={CONFIG.icon} width={48} height={48} className="br mr-auto ml-auto" />
|
|
<div className="flex flex-col g16 items-center">
|
|
<h1>
|
|
<FormattedMessage defaultMessage="Sign Up" />
|
|
</h1>
|
|
<FormattedMessage defaultMessage="What should we call you?" />
|
|
</div>
|
|
<form onSubmit={onSubmit} className="flex flex-col g16">
|
|
<input
|
|
type="text"
|
|
autoFocus={true}
|
|
placeholder={formatMessage({
|
|
defaultMessage: "Name or nym",
|
|
id: "aHje0o",
|
|
})}
|
|
value={name}
|
|
onChange={onChange}
|
|
className="new-username"
|
|
/>
|
|
<AsyncButton className="primary" disabled={name.length === 0} onClick={onSubmit}>
|
|
{CONFIG.signUp.quickStart ? (
|
|
<FormattedMessage
|
|
description="Button text after entering username in quick signup"
|
|
defaultMessage="Go"
|
|
id="0zASjL"
|
|
/>
|
|
) : (
|
|
<FormattedMessage defaultMessage="Next" />
|
|
)}
|
|
</AsyncButton>
|
|
</form>
|
|
<div className="flex flex-col g16 items-center">
|
|
<Link to={"/login"}>
|
|
<FormattedMessage defaultMessage="Already have an account?" />
|
|
</Link>
|
|
<AsyncButton className="secondary" onClick={() => navigate("/login")}>
|
|
<FormattedMessage defaultMessage="Sign In" />
|
|
</AsyncButton>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|