161 lines
4.6 KiB
TypeScript
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>
|
|
</>
|
|
);
|
|
}
|
|
}
|
|
}
|