Files
zap.stream/src/element/login-signup.tsx
2023-08-27 16:25:27 +01:00

161 lines
4.6 KiB
TypeScript

import "./login-signup.css";
import { CSSProperties, useState } from "react";
import { EventPublisher, UserMetadata } from "@snort/system";
import { schnorr } from "@noble/curves/secp256k1";
import { bytesToHex } from "@noble/curves/abstract/utils";
import AsyncButton from "./async-button";
import { Login, System } from "index";
import { Icon } from "./icon";
import Copy from "./copy";
import { hexToBech32, openFile } from "utils";
import { VoidApi } from "@void-cat/api";
import { LoginType } from "login";
import { FormattedMessage } from "react-intl";
enum Stage {
Login = 0,
Details = 1,
SaveKey = 2,
}
export function LoginSignup({ close }: { close: () => void }) {
const [error, setError] = useState("");
const [stage, setStage] = useState(Stage.Login);
const [username, setUsername] = useState("");
const [avatar, setAvatar] = useState("");
const [key, setNewKey] = useState("");
async function doLogin() {
try {
const pub = await EventPublisher.nip7();
if (pub) {
Login.loginWithPubkey(pub.pubKey, LoginType.Nip7);
close();
}
} catch (e) {
console.error(e);
if (e instanceof Error) {
setError(e.message);
} else {
setError(e as string);
}
}
}
function createAccount() {
const newKey = bytesToHex(schnorr.utils.randomPrivateKey());
setNewKey(newKey);
setStage(Stage.Details);
}
function loginWithKey() {
Login.loginWithPrivateKey(key);
close();
}
async function uploadAvatar() {
const file = await openFile();
if (file) {
const VoidCatHost = "https://void.cat";
const api = new VoidApi(VoidCatHost);
const uploader = api.getUploader(file);
const result = await uploader.upload({
"V-Strip-Metadata": "true",
});
if (result.ok) {
const resultUrl = result.file?.metadata?.url ?? `${VoidCatHost}/d/${result.file?.id}`;
setAvatar(resultUrl);
} else {
setError(result.errorMessage ?? "Upload failed");
}
}
}
async function saveProfile() {
const pub = EventPublisher.privateKey(key);
const profile = {
name: username,
picture: avatar,
lud16: `${pub.pubKey}@zap.stream`,
} as UserMetadata;
const ev = await pub.metadata(profile);
console.debug(ev);
System.BroadcastEvent(ev);
setStage(Stage.SaveKey);
}
switch (stage) {
case Stage.Login: {
return (
<>
<h2>
<FormattedMessage defaultMessage="Login" />
</h2>
{"nostr" in window && (
<AsyncButton type="button" className="btn btn-primary" onClick={doLogin}>
<FormattedMessage defaultMessage="Nostr Extension" />
</AsyncButton>
)}
<button type="button" className="btn btn-primary" onClick={createAccount}>
<FormattedMessage defaultMessage="Create Account" />
</button>
{error && <b className="error">{error}</b>}
</>
);
}
case Stage.Details: {
return (
<>
<h2>
<FormattedMessage defaultMessage="Setup Profile" />
</h2>
<div className="flex f-center">
<div
className="avatar-input"
onClick={uploadAvatar}
style={
{
"--img": `url(${avatar})`,
} as CSSProperties
}>
<Icon name="camera-plus" />
</div>
</div>
<div>
<div className="paper">
<input type="text" placeholder="Username" value={username} onChange={e => setUsername(e.target.value)} />
</div>
<small>
<FormattedMessage defaultMessage="You can change this later" />
</small>
</div>
<AsyncButton type="button" className="btn btn-primary" onClick={saveProfile}>
<FormattedMessage defaultMessage="Save" />
</AsyncButton>
</>
);
}
case Stage.SaveKey: {
return (
<>
<h2>
<FormattedMessage defaultMessage="Save Key" />
</h2>
<p>
<FormattedMessage defaultMessage="Nostr uses private keys, please save yours, if you lose this key you wont be able to login to your account anymore!" />
</p>
<div className="paper">
<Copy text={hexToBech32("nsec", key)} />
</div>
<button type="button" className="btn btn-primary" onClick={loginWithKey}>
<FormattedMessage defaultMessage="Ok, it's safe" />
</button>
</>
);
}
}
}