blowater/UI/signIn.tsx
2023-10-21 13:04:04 +08:00

234 lines
8.0 KiB
TypeScript

/** @jsx h */
import { Component, h } from "https://esm.sh/preact@10.17.1";
import { tw } from "https://esm.sh/twind@0.16.16";
import { GetLocalStorageAccountContext, Nip7ExtensionContext } from "./account-context.ts";
import { ButtonClass, CenterClass, DividerClass } from "./components/tw.ts";
import KeyView from "./key-view.tsx";
import { PrivateKey } from "../lib/nostr-ts/key.ts";
import { InMemoryAccountContext } from "../lib/nostr-ts/nostr.ts";
import { emitFunc, EventEmitter } from "../event-bus.ts";
export type SignInEvent = {
type: "signin";
privateKey?: PrivateKey; // undefined means sign in with extentions
} | {
type: "editSignInPrivateKey";
privateKey: string;
};
const AlbyURL = "https://getalby.com/";
type SignInState = "none" | "nip07" | "local";
export function getSignInState(): SignInState {
const state = localStorage.getItem("SignInState");
if (state === null) {
return "none";
}
return state as SignInState;
}
export function setSignInState(state: SignInState) {
localStorage.setItem("SignInState", state);
}
////////////////////////
// Check Login Status //
////////////////////////
export async function getCurrentSignInCtx() {
if (getSignInState() === "nip07") {
const albyCtx = await Nip7ExtensionContext.New();
if (albyCtx instanceof Error) {
return albyCtx;
}
if (albyCtx === undefined) {
setSignInState("none");
}
return albyCtx;
}
if (getSignInState() === "local") {
const ctx = GetLocalStorageAccountContext();
if (ctx instanceof Error) {
throw ctx;
}
if (ctx === undefined) {
console.log("GetLocalStorageAccountContext is undefined");
setSignInState("none");
}
return ctx;
}
return undefined;
}
export async function signInWithExtension() {
const albyCtx = await Nip7ExtensionContext.New();
if (albyCtx instanceof Error) {
return albyCtx;
}
if (albyCtx === undefined) {
open(AlbyURL);
}
return albyCtx;
}
export function signInWithPrivateKey(privateKey: PrivateKey) {
const ctx = InMemoryAccountContext.New(privateKey);
if (ctx instanceof Error) {
throw ctx;
}
localStorage.setItem("MPK", privateKey.hex);
setSignInState("local");
return ctx;
}
type Props = {
eventBus: EventEmitter<SignInEvent>;
} & SignInModel;
export type SignInModel = {
privateKey: string;
warningString?: string;
};
type State = { state: "newAccount" | "enterPrivateKey" };
export class SignIn extends Component<Props, State> {
render() {
const props = this.props;
if (this.state.state == "newAccount") {
const privateKey = PrivateKey.Generate();
return (
<div
class={tw`fixed inset-0 bg-[#313338] flex items-center justify-center px-4`}
>
<div class={tw`flex flex-col w-[40rem]`}>
<KeyView
privateKey={privateKey}
publicKey={privateKey.toPublicKey()}
/>
<button
onClick={on_BackToSignInPage_click(this.setState.bind(this))}
class={tw`w-full mt-8 bg-[#404249] hover:bg-[#2B2D31] ${ButtonClass}`}
>
Back
</button>
<button
onClick={() => {
props.eventBus.emit({
type: "signin",
privateKey: privateKey,
});
}}
class={tw`w-full mt-8 bg-[#ED4545] hover:bg-[#E03030] ${ButtonClass}`}
>
Sign In
</button>
</div>
</div>
);
}
let privateKey = PrivateKey.FromHex(props.privateKey);
if (privateKey instanceof Error) {
privateKey = PrivateKey.FromBech32(props.privateKey);
}
return (
<div
class={tw`h-screen w-screen bg-[#313338] flex items-center justify-center p-4 overflow-y-auto`}
>
<div class={tw`w-[40rem]`}>
<img
class={tw`w-32 h-32 mx-auto`}
src="logo.png"
alt="Logo"
/>
<h1
class={tw`text-[#F3F4EA] text-center text-[2rem]`}
>
Welcome to Blowater
</h1>
<input
onInput={on_EditSignInPrivateKey_clicked(props.eventBus.emit)}
placeholder="Input your private key here"
type="password"
class={tw`w-full px-4 py-2 focus-visible:outline-none rounded-lg mt-8`}
/>
{privateKey instanceof Error
? (
<p class={tw`text-[#F3F4EA] mt-2`}>
Private Key has to be 64 letters hex-decimal or 63 letters nsec string
</p>
)
: undefined}
<button
onClick={on_SignIn_clicked(privateKey, props.eventBus.emit)}
disabled={privateKey instanceof Error}
class={tw`w-full bg-[#2B2D31] hover:bg-[#404249] mt-4 disabled:bg-[#404249] ${ButtonClass}`}
>
Sign In
</button>
<div class={tw`h-16 w-full relative ${CenterClass}`}>
<div class={tw`${DividerClass}`}></div>
<div class={tw`absolute w-full h-full ${CenterClass}`}>
<span class={tw`bg-[#313338] px-2 text-[#F3F4EA]`}>Or you can</span>
</div>
</div>
<button
onClick={() => {
props.eventBus.emit({
type: "signin",
});
}}
class={tw`${ButtonClass} ${CenterClass} w-full bg-[#F8C455] text-[#313338] hover:bg-[#FFDF6F] py-0`}
>
<img
class={tw`h-10`}
src="alby-logo.svg"
alt="Alby Logo"
/>
Sign in with Alby
</button>
<button
onClick={on_CreateAccount_clicked(this.setState.bind(this))}
class={tw`${ButtonClass} w-full bg-[#2B2D31] hover:bg-[#404249] mt-4`}
>
Create an account
</button>
{props.warningString
? <p class={tw`text-[#F3F4EA] mt-2`}>{props.warningString}</p>
: undefined}
</div>
</div>
);
}
}
const on_CreateAccount_clicked = (setState: (state: State) => void) => () => {
setState({ state: "newAccount" });
};
const on_SignIn_clicked = (privateKey: PrivateKey | Error, emit: emitFunc<SignInEvent>) => () => {
if (privateKey instanceof PrivateKey) {
emit({
type: "signin",
privateKey: privateKey,
});
}
};
const on_EditSignInPrivateKey_clicked =
(emit: emitFunc<SignInEvent>) => (e: h.JSX.TargetedEvent<HTMLInputElement, Event>) => {
emit({
type: "editSignInPrivateKey",
privateKey: e.currentTarget.value,
});
};
const on_BackToSignInPage_click = (setState: (state: State) => void) => () => {
setState({ state: "enterPrivateKey" });
};