175 lines
4.6 KiB
TypeScript
175 lines
4.6 KiB
TypeScript
import {
|
|
EventPublisher,
|
|
Nip46Signer,
|
|
Nip7Signer,
|
|
PrivateKeySigner,
|
|
} from "@snort/system";
|
|
import { AsyncButton } from "../components/button";
|
|
import { useContext, useState } from "react";
|
|
import { bech32ToHex, hexToBech32 } from "@snort/shared";
|
|
import { openFile } from "../utils";
|
|
import { SnortContext } from "@snort/system-react";
|
|
import { Blossom } from "../blossom";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { LoginState } from "../login";
|
|
|
|
export default function SignUpPage() {
|
|
const [name, setName] = useState("");
|
|
const [keyIn, setKeyIn] = useState("");
|
|
const [error, setError] = useState("");
|
|
const [file, setFile] = useState<File>();
|
|
const [key, setKey] = useState<PrivateKeySigner>();
|
|
const system = useContext(SnortContext);
|
|
const navigate = useNavigate();
|
|
|
|
async function uploadImage() {
|
|
const f = await openFile();
|
|
setFile(f);
|
|
}
|
|
|
|
async function spawnAccount() {
|
|
if (!key) return;
|
|
setError("");
|
|
const pub = new EventPublisher(key, key.getPubKey());
|
|
|
|
let pic = undefined;
|
|
if (file) {
|
|
// upload picture
|
|
const b = new Blossom("https://nostr.download", pub);
|
|
const up = await b.upload(file);
|
|
if (up.url) {
|
|
pic = up.url;
|
|
} else {
|
|
setError("Upload filed");
|
|
return;
|
|
}
|
|
}
|
|
const ev = await pub.metadata({
|
|
name: name,
|
|
picture: pic,
|
|
});
|
|
system.BroadcastEvent(ev);
|
|
LoginState.loginPrivateKey(key.privateKey);
|
|
navigate("/");
|
|
}
|
|
|
|
async function loginKey() {
|
|
setError("");
|
|
try {
|
|
if (keyIn.startsWith("nsec1")) {
|
|
LoginState.loginPrivateKey(bech32ToHex(keyIn));
|
|
navigate("/");
|
|
} else if (keyIn.startsWith("bunker://")) {
|
|
const signer = new Nip46Signer(keyIn);
|
|
await signer.init();
|
|
const pubkey = await signer.getPubKey();
|
|
LoginState.loginBunker(keyIn, signer.privateKey!, pubkey);
|
|
navigate("/");
|
|
}
|
|
} catch (e) {
|
|
if (e instanceof Error) {
|
|
setError(e.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
{error && <b className="text-red-500">{error}</b>}
|
|
<h1>Login</h1>
|
|
<input
|
|
type="text"
|
|
placeholder="nsec/bunker"
|
|
value={keyIn}
|
|
onChange={(e) => setKeyIn(e.target.value)}
|
|
/>
|
|
<AsyncButton
|
|
onClick={loginKey}
|
|
disabled={!keyIn.startsWith("nsec") && !keyIn.startsWith("bunker://")}
|
|
>
|
|
Login
|
|
</AsyncButton>
|
|
{window.nostr && (
|
|
<div className="flex flex-col gap-4">
|
|
Browser Extension:
|
|
<AsyncButton
|
|
onClick={async () => {
|
|
const pk = await new Nip7Signer().getPubKey();
|
|
LoginState.login(pk);
|
|
navigate("/");
|
|
}}
|
|
>
|
|
Nostr Extension
|
|
</AsyncButton>
|
|
</div>
|
|
)}
|
|
<div className="flex gap-4 items-center my-6">
|
|
<div className="text-xl">OR</div>
|
|
<div className="h-[1px] bg-neutral-800 w-full"></div>
|
|
</div>
|
|
|
|
<h1>Create Account</h1>
|
|
|
|
<p>
|
|
LNVPS uses nostr accounts,{" "}
|
|
<a
|
|
href="https://nostr.how/en/what-is-nostr"
|
|
target="_blank"
|
|
className="underline"
|
|
>
|
|
what is nostr?
|
|
</a>
|
|
</p>
|
|
<div className="flex flex-col gap-2">
|
|
<div>Avatar</div>
|
|
<div
|
|
className="w-40 h-40 bg-neutral-900 rounded-xl relative cursor-pointer overflow-hidden"
|
|
onClick={uploadImage}
|
|
>
|
|
<div className="absolute bg-black/50 w-full h-full hover:opacity-90 opacity-0 flex items-center justify-center">
|
|
Upload
|
|
</div>
|
|
{file && (
|
|
<img
|
|
src={URL.createObjectURL(file)}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<div>Name</div>
|
|
<div>
|
|
<input
|
|
type="text"
|
|
placeholder="Display name"
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<AsyncButton
|
|
onClick={async () => {
|
|
setKey(PrivateKeySigner.random());
|
|
}}
|
|
>
|
|
Create Account
|
|
</AsyncButton>
|
|
|
|
{key && (
|
|
<>
|
|
<div className="flex flex-col gap-2">
|
|
<h3>Your new key:</h3>
|
|
<div className="font-monospace select-all">
|
|
{hexToBech32("nsec", key.privateKey)}
|
|
</div>
|
|
<b>Please save this key, it CANNOT be recovered</b>
|
|
</div>
|
|
<AsyncButton onClick={spawnAccount}>Login</AsyncButton>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|