wip: improve onboarding

This commit is contained in:
reya 2024-01-25 15:25:40 +07:00
parent bd45c36072
commit 74426e13c8
9 changed files with 446 additions and 359 deletions

View File

@ -207,9 +207,6 @@ export default function Router() {
}, },
{ {
path: "create", path: "create",
loader: async () => {
return await ark.getOAuthServices();
},
async lazy() { async lazy() {
const { CreateAccountScreen } = await import( const { CreateAccountScreen } = await import(
"./routes/auth/create" "./routes/auth/create"
@ -217,6 +214,27 @@ export default function Router() {
return { Component: CreateAccountScreen }; return { Component: CreateAccountScreen };
}, },
}, },
{
path: "create-keys",
async lazy() {
const { CreateAccountKeys } = await import(
"./routes/auth/create-keys"
);
return { Component: CreateAccountKeys };
},
},
{
path: "create-address",
loader: async () => {
return await ark.getOAuthServices();
},
async lazy() {
const { CreateAccountAddress } = await import(
"./routes/auth/create-address"
);
return { Component: CreateAccountAddress };
},
},
{ {
path: "login", path: "login",
async lazy() { async lazy() {

View File

@ -0,0 +1,261 @@
import { useArk } from "@lume/ark";
import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons";
import { useStorage } from "@lume/storage";
import { onboardingAtom } from "@lume/utils";
import NDK, {
NDKEvent,
NDKKind,
NDKNip46Signer,
NDKPrivateKeySigner,
} from "@nostr-dev-kit/ndk";
import * as Select from "@radix-ui/react-select";
import { UnlistenFn } from "@tauri-apps/api/event";
import { Window } from "@tauri-apps/api/window";
import { useSetAtom } from "jotai";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { useLoaderData, useNavigate } from "react-router-dom";
import { toast } from "sonner";
const Item = ({ event }: { event: NDKEvent }) => {
const domain = JSON.parse(event.content).nip05.replace("_@", "");
return (
<Select.Item
value={event.id}
className="relative flex items-center pr-10 leading-none rounded-md select-none text-neutral-100 rounded-mg h-9 pl-7"
>
<Select.ItemText>@{domain}</Select.ItemText>
<Select.ItemIndicator className="absolute left-0 inline-flex items-center justify-center transform h-7">
<CheckIcon className="size-4" />
</Select.ItemIndicator>
</Select.Item>
);
};
export function CreateAccountAddress() {
const ark = useArk();
const storage = useStorage();
const services = useLoaderData() as NDKEvent[];
const setOnboarding = useSetAtom(onboardingAtom);
const navigate = useNavigate();
const [serviceId, setServiceId] = useState(services?.[0]?.id);
const [loading, setIsLoading] = useState(false);
const {
register,
handleSubmit,
formState: { isValid },
} = useForm();
const getDomainName = (id: string) => {
const event = services.find((ev) => ev.id === id);
return JSON.parse(event.content).nip05.replace("_@", "") as string;
};
const onSubmit = async (data: { username: string; email: string }) => {
try {
setIsLoading(true);
const domain = getDomainName(serviceId);
const service = services.find((ev) => ev.id === serviceId);
// generate ndk for nsecbunker
const localSigner = NDKPrivateKeySigner.generate();
const bunker = new NDK({
explicitRelayUrls: [
"wss://relay.nsecbunker.com/",
"wss://nostr.vulpem.com/",
],
});
await bunker.connect(2000);
// generate tmp remote singer for create account
const remoteSigner = new NDKNip46Signer(
bunker,
service.pubkey,
localSigner,
);
// handle auth url request
let unlisten: UnlistenFn;
let authWindow: Window;
let account: string = undefined;
remoteSigner.addListener("authUrl", async (authUrl: string) => {
authWindow = new Window(`auth-${serviceId}`, {
url: authUrl,
title: domain,
titleBarStyle: "overlay",
width: 600,
height: 650,
center: true,
closable: false,
});
unlisten = await authWindow.onCloseRequested(() => {
if (!account) {
setIsLoading(false);
unlisten();
return authWindow.close();
}
});
});
// create new account
account = await remoteSigner.createAccount(
data.username,
domain,
data.email,
);
if (!account) {
unlisten();
setIsLoading(false);
authWindow.close();
return toast.error("Failed to create new account, try again later");
}
unlisten();
authWindow.close();
// add account to storage
await storage.createSetting("nsecbunker", "1");
const dbAccount = await storage.createAccount({
pubkey: account,
privkey: localSigner.privateKey,
});
ark.account = dbAccount;
// get final signer with newly created account
const finalSigner = new NDKNip46Signer(bunker, account, localSigner);
await finalSigner.blockUntilReady();
// update main ndk instance signer
ark.updateNostrSigner({ signer: finalSigner });
// remove default nsecbunker profile and contact list
// await ark.createEvent({ kind: NDKKind.Metadata, content: "", tags: [] });
await ark.createEvent({ kind: NDKKind.Contacts, content: "", tags: [] });
setIsLoading(false);
setOnboarding({ open: true, newUser: true });
return navigate("/auth/onboarding", { replace: true });
} catch (e) {
setIsLoading(false);
toast.error(String(e));
}
};
return (
<div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold">Create Account</h1>
</div>
{!services ? (
<div className="flex items-center justify-center w-full">
<LoaderIcon className="size-5 animate-spin" />
</div>
) : (
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3 mb-0"
>
<div className="flex flex-col gap-6 p-5 bg-neutral-950 rounded-2xl">
<div className="flex flex-col gap-2">
<label
htmlFor="username"
className="text-sm font-semibold uppercase text-neutral-600"
>
Username *
</label>
<div className="flex flex-col gap-1.5">
<div className="flex items-center justify-between w-full gap-2 bg-neutral-900 rounded-xl">
<input
type={"text"}
{...register("username", {
required: true,
minLength: 1,
})}
spellCheck={false}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
placeholder="alice"
className="flex-1 min-w-0 text-xl bg-transparent border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-14 ring-0 placeholder:text-neutral-600"
/>
<Select.Root value={serviceId} onValueChange={setServiceId}>
<Select.Trigger className="inline-flex items-center justify-end gap-2 pr-3 text-xl font-semibold text-blue-500 w-max shrink-0">
<Select.Value>@{getDomainName(serviceId)}</Select.Value>
<Select.Icon>
<ChevronDownIcon className="size-5" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="rounded-lg border border-white/20 bg-white/10 backdrop-blur-xl">
<Select.Viewport className="p-3">
<Select.Group>
<Select.Label className="mb-2 text-sm font-medium uppercase px-7 text-neutral-600">
Choose a Provider
</Select.Label>
{services.map((service) => (
<Item key={service.id} event={service} />
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
</div>
<span className="text-sm text-neutral-600">
Use to login to Lume and other Nostr apps. You can choose
provider you trust to manage your account
</span>
</div>
</div>
<div className="flex flex-col gap-1.5">
<div className="flex flex-col gap-2">
<label
htmlFor="email"
className="text-sm font-semibold uppercase text-neutral-600"
>
Backup Email (optional)
</label>
<input
type={"email"}
{...register("email", { required: false })}
spellCheck={false}
autoCapitalize="none"
autoCorrect="none"
className="px-3 text-xl border-transparent rounded-xl h-14 bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-800"
/>
</div>
<span className="text-sm text-neutral-600">
Use for recover your account if you lose your password
</span>
</div>
</div>
<div>
<button
type="submit"
disabled={!isValid}
className="inline-flex items-center justify-center w-full text-lg h-12 font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600 disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="size-5 animate-spin" />
) : (
"Continue"
)}
</button>
</div>
</form>
)}
</div>
</div>
);
}

View File

@ -0,0 +1,91 @@
import { useArk } from "@lume/ark";
import { useStorage } from "@lume/storage";
import { onboardingAtom } from "@lume/utils";
import { NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";
import { desktopDir } from "@tauri-apps/api/path";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { useSetAtom } from "jotai";
import { nanoid } from "nanoid";
import { getPublicKey, nip19 } from "nostr-tools";
import { useNavigate } from "react-router-dom";
import { toast } from "sonner";
export function CreateAccountKeys() {
const ark = useArk();
const storage = useStorage();
const setOnboarding = useSetAtom(onboardingAtom);
const navigate = useNavigate();
const generateNostrKeys = async () => {
const signer = NDKPrivateKeySigner.generate();
const pubkey = getPublicKey(signer.privateKey);
const npub = nip19.npubEncode(pubkey);
const nsec = nip19.nsecEncode(signer.privateKey);
ark.updateNostrSigner({ signer });
const downloadPath = await desktopDir();
const fileName = `nostr_keys_${nanoid(4)}.txt`;
const filePath = await save({
defaultPath: `${downloadPath}/${fileName}`,
});
if (!filePath) {
return toast.info("You need to save account keys before continue.");
}
await writeTextFile(
filePath,
`Nostr Account\nGenerated by Lume (lume.nu)\n---\nPublic key: ${npub}\nPrivate key: ${nsec}`,
);
await storage.createAccount({
pubkey: pubkey,
privkey: signer.privateKey,
});
setOnboarding({ open: true, newUser: true });
return navigate("/auth/onboarding", { replace: true });
};
return (
<div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold">Create Account</h1>
</div>
<div className="flex flex-col gap-3 mb-0">
<div className="flex flex-col gap-2">
<label
htmlFor="npub"
className="text-sm font-semibold uppercase text-neutral-600"
>
Public Key
</label>
<input
readOnly
type="text"
className="px-3 text-xl border-transparent rounded-xl h-14 bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-800"
/>
</div>
<div className="flex flex-col gap-2">
<label
htmlFor="nsec"
className="text-sm font-semibold uppercase text-neutral-600"
>
Private Key
</label>
<input
readOnly
type="text"
className="px-3 text-xl border-transparent rounded-xl h-14 bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-800"
/>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,342 +1,74 @@
import { useArk } from "@lume/ark"; import { LoaderIcon } from "@lume/icons";
import { CheckIcon, ChevronDownIcon, LoaderIcon } from "@lume/icons"; import { cn } from "@lume/utils";
import { useStorage } from "@lume/storage";
import { onboardingAtom } from "@lume/utils";
import NDK, {
NDKEvent,
NDKKind,
NDKNip46Signer,
NDKPrivateKeySigner,
} from "@nostr-dev-kit/ndk";
import * as Select from "@radix-ui/react-select";
import { UnlistenFn } from "@tauri-apps/api/event";
import { desktopDir } from "@tauri-apps/api/path";
import { Window } from "@tauri-apps/api/window";
import { save } from "@tauri-apps/plugin-dialog";
import { writeTextFile } from "@tauri-apps/plugin-fs";
import { useSetAtom } from "jotai";
import { nanoid } from "nanoid";
import { getPublicKey, nip19 } from "nostr-tools";
import { useState } from "react"; import { useState } from "react";
import { useForm } from "react-hook-form"; import { Link, useNavigate } from "react-router-dom";
import { useLoaderData, useNavigate } from "react-router-dom";
import { toast } from "sonner";
const Item = ({ event }: { event: NDKEvent }) => {
const domain = JSON.parse(event.content).nip05.replace("_@", "");
return (
<Select.Item
value={event.id}
className="relative flex items-center pr-10 leading-none rounded-md select-none text-neutral-100 rounded-mg h-9 pl-7"
>
<Select.ItemText>@{domain}</Select.ItemText>
<Select.ItemIndicator className="absolute left-0 inline-flex items-center justify-center transform h-7">
<CheckIcon className="size-4" />
</Select.ItemIndicator>
</Select.Item>
);
};
export function CreateAccountScreen() { export function CreateAccountScreen() {
const ark = useArk();
const storage = useStorage();
const services = useLoaderData() as NDKEvent[];
const setOnboarding = useSetAtom(onboardingAtom);
const navigate = useNavigate(); const navigate = useNavigate();
const [serviceId, setServiceId] = useState(services?.[0]?.id); const [method, setMethod] = useState<"self" | "managed">("self");
const [loading, setIsLoading] = useState(false); const [loading, setLoading] = useState(false);
const { const next = () => {
register, setLoading(true);
handleSubmit,
formState: { isValid },
} = useForm();
const getDomainName = (id: string) => { if (method === "self") {
const event = services.find((ev) => ev.id === id); navigate("/auth/create-keys");
return JSON.parse(event.content).nip05.replace("_@", "") as string; } else {
}; navigate("/auth/create-address");
const generateNostrKeys = async () => {
const signer = NDKPrivateKeySigner.generate();
const pubkey = getPublicKey(signer.privateKey);
const npub = nip19.npubEncode(pubkey);
const nsec = nip19.nsecEncode(signer.privateKey);
ark.updateNostrSigner({ signer });
const downloadPath = await desktopDir();
const fileName = `nostr_keys_${nanoid(4)}.txt`;
const filePath = await save({
defaultPath: `${downloadPath}/${fileName}`,
});
if (!filePath) {
return toast.info("You need to save account keys before continue.");
}
await writeTextFile(
filePath,
`Nostr Account\nGenerated by Lume (lume.nu)\n---\nPublic key: ${npub}\nPrivate key: ${nsec}`,
);
await storage.createAccount({
pubkey: pubkey,
privkey: signer.privateKey,
});
setOnboarding({ open: true, newUser: true });
return navigate("/auth/onboarding", { replace: true });
};
const onSubmit = async (data: { username: string; email: string }) => {
try {
setIsLoading(true);
const domain = getDomainName(serviceId);
const service = services.find((ev) => ev.id === serviceId);
// generate ndk for nsecbunker
const localSigner = NDKPrivateKeySigner.generate();
const bunker = new NDK({
explicitRelayUrls: [
"wss://relay.nsecbunker.com/",
"wss://nostr.vulpem.com/",
],
});
await bunker.connect(2000);
// generate tmp remote singer for create account
const remoteSigner = new NDKNip46Signer(
bunker,
service.pubkey,
localSigner,
);
// handle auth url request
let unlisten: UnlistenFn;
let authWindow: Window;
let account: string = undefined;
remoteSigner.addListener("authUrl", async (authUrl: string) => {
authWindow = new Window(`auth-${serviceId}`, {
url: authUrl,
title: domain,
titleBarStyle: "overlay",
width: 600,
height: 650,
center: true,
closable: false,
});
unlisten = await authWindow.onCloseRequested(() => {
if (!account) {
setIsLoading(false);
unlisten();
return authWindow.close();
}
});
});
// create new account
account = await remoteSigner.createAccount(
data.username,
domain,
data.email,
);
if (!account) {
unlisten();
setIsLoading(false);
authWindow.close();
return toast.error("Failed to create new account, try again later");
}
unlisten();
authWindow.close();
// add account to storage
await storage.createSetting("nsecbunker", "1");
const dbAccount = await storage.createAccount({
pubkey: account,
privkey: localSigner.privateKey,
});
ark.account = dbAccount;
// get final signer with newly created account
const finalSigner = new NDKNip46Signer(bunker, account, localSigner);
await finalSigner.blockUntilReady();
// update main ndk instance signer
ark.updateNostrSigner({ signer: finalSigner });
// remove default nsecbunker profile and contact list
// await ark.createEvent({ kind: NDKKind.Metadata, content: "", tags: [] });
await ark.createEvent({ kind: NDKKind.Contacts, content: "", tags: [] });
setIsLoading(false);
setOnboarding({ open: true, newUser: true });
return navigate("/auth/onboarding", { replace: true });
} catch (e) {
setIsLoading(false);
toast.error(String(e));
} }
}; };
return ( return (
<div className="relative flex items-center justify-center w-full h-full"> <div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-8 mx-auto"> <div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center"> <div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold"> <h1 className="text-2xl font-semibold">
Let's get you set up on Nostr. Let's get you set up on Nostr.
</h1> </h1>
<p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500">
Choose one of methods below to create your account
</p>
</div>
<div className="flex flex-col gap-4">
<button
type="button"
onClick={() => setMethod("managed")}
className={cn(
"flex flex-col items-start px-4 py-3.5 bg-neutral-900 rounded-xl hover:bg-neutral-800",
method === "managed" ? "ring-1 ring-teal-500" : "",
)}
>
<p className="font-semibold">Managed by Provider</p>
<p className="text-sm font-medium text-neutral-500">
A 3rd party provider will handle your sign in keys for you.
</p>
</button>
<button
type="button"
onClick={() => setMethod("self")}
className={cn(
"flex flex-col items-start px-4 py-3.5 bg-neutral-900 rounded-xl hover:bg-neutral-800",
method === "self" ? "ring-1 ring-teal-500" : "",
)}
>
<p className="font-semibold">Self-Managed</p>
<p className="text-sm font-medium text-neutral-500">
You create your keys and keep them safe.
</p>
</button>
<button
type="button"
onClick={next}
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600"
>
{loading ? (
<LoaderIcon className="size-5 animate-spin" />
) : (
"Continue"
)}
</button>
</div> </div>
{!services ? (
<div className="flex items-center justify-center w-full">
<LoaderIcon className="size-5 animate-spin" />
</div>
) : (
<div className="flex flex-col gap-6">
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-col gap-3 mb-0"
>
<div className="flex flex-col gap-6 p-5 bg-neutral-950 rounded-2xl">
<div className="flex flex-col gap-2">
<label
htmlFor="username"
className="text-sm font-semibold uppercase text-neutral-600"
>
Username *
</label>
<div className="flex flex-col gap-1.5">
<div className="flex items-center justify-between w-full gap-2 bg-neutral-900 rounded-xl">
<input
type={"text"}
{...register("username", {
required: true,
minLength: 1,
})}
spellCheck={false}
autoComplete="off"
autoCorrect="off"
autoCapitalize="off"
placeholder="alice"
className="flex-1 min-w-0 text-xl bg-transparent border-transparent outline-none focus:outline-none focus:ring-0 focus:border-none h-14 ring-0 placeholder:text-neutral-600"
/>
<Select.Root
value={serviceId}
onValueChange={setServiceId}
>
<Select.Trigger className="inline-flex items-center justify-end gap-2 pr-3 text-xl font-semibold text-blue-500 w-max shrink-0">
<Select.Value>
@{getDomainName(serviceId)}
</Select.Value>
<Select.Icon>
<ChevronDownIcon className="size-5" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="rounded-lg border border-white/20 bg-white/10 backdrop-blur-xl">
<Select.Viewport className="p-3">
<Select.Group>
<Select.Label className="mb-2 text-sm font-medium uppercase px-7 text-neutral-600">
Choose a Provider
</Select.Label>
{services.map((service) => (
<Item key={service.id} event={service} />
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
</div>
<span className="text-sm text-neutral-600">
Use to login to Lume and other Nostr apps. You can choose
provider you trust to manage your account
</span>
</div>
</div>
<div className="flex flex-col gap-1.5">
<div className="flex flex-col gap-2">
<label
htmlFor="email"
className="text-sm font-semibold uppercase text-neutral-600"
>
Backup Email (optional)
</label>
<input
type={"email"}
{...register("email", { required: false })}
spellCheck={false}
autoCapitalize="none"
autoCorrect="none"
className="px-3 text-xl border-transparent rounded-xl h-14 bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-800"
/>
</div>
<span className="text-sm text-neutral-600">
Use for recover your account if you lose your password
</span>
</div>
</div>
<div>
<button
type="submit"
disabled={!isValid}
className="inline-flex items-center justify-center w-full text-lg h-12 font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600 disabled:opacity-50"
>
{loading ? (
<LoaderIcon className="size-5 animate-spin" />
) : (
"Create Account"
)}
</button>
</div>
</form>
<div className="flex flex-col gap-6">
<div className="flex flex-col">
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-neutral-900" />
</div>
<div className="relative flex justify-center">
<span className="px-2 font-medium bg-black text-neutral-500">
Or manage your own keys
</span>
</div>
</div>
<span className="mx-auto text-xs font-medium bg-black text-neutral-600">
Mostly compatible with other Nostr clients
</span>
</div>
<div>
<button
type="button"
onClick={generateNostrKeys}
className="mb-2 inline-flex items-center justify-center w-full h-12 text-lg font-medium text-neutral-50 rounded-xl bg-neutral-950 hover:bg-neutral-900"
>
Generate Nostr Keys
</button>
<p className="text-sm text-center text-neutral-500">
If you are using this option, please make sure to store your
keys safely. You{" "}
<span className="text-red-600">cannot recover</span> them if
they're lost, and will be{" "}
<span className="text-red-600">unable</span> to access your
account.
</p>
</div>
</div>
</div>
)}
</div> </div>
</div> </div>
); );

View File

@ -50,7 +50,7 @@ export function LoginWithKey() {
return ( return (
<div className="relative flex items-center justify-center w-full h-full"> <div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-8 mx-auto"> <div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center"> <div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold">Enter your Private Key</h1> <h1 className="text-2xl font-semibold">Enter your Private Key</h1>
<p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500"> <p className="text-lg font-medium leading-snug text-neutral-600 dark:text-neutral-500">

View File

@ -66,7 +66,7 @@ export function LoginWithNsecbunker() {
return ( return (
<div className="relative flex items-center justify-center w-full h-full"> <div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-8 mx-auto"> <div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center"> <div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold"> <h1 className="text-2xl font-semibold">
Enter your nsecbunker token Enter your nsecbunker token

View File

@ -128,9 +128,9 @@ export function LoginWithOAuth() {
return ( return (
<div className="relative flex items-center justify-center w-full h-full"> <div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-8 mx-auto"> <div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center"> <div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold">Enter your NIP-05 address</h1> <h1 className="text-2xl font-semibold">Enter your Nostr Address</h1>
</div> </div>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<form <form

View File

@ -3,11 +3,9 @@ import { Link } from "react-router-dom";
export function LoginScreen() { export function LoginScreen() {
return ( return (
<div className="relative flex items-center justify-center w-full h-full"> <div className="relative flex items-center justify-center w-full h-full">
<div className="flex flex-col w-full max-w-md gap-8 mx-auto"> <div className="flex flex-col w-full max-w-md gap-16 mx-auto">
<div className="flex flex-col gap-1 text-center items-center"> <div className="flex flex-col gap-1 text-center items-center">
<h1 className="text-2xl font-semibold"> <h1 className="text-2xl font-semibold">Welcome back, anon!</h1>
Continue your experience on Nostr
</h1>
</div> </div>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
@ -15,13 +13,13 @@ export function LoginScreen() {
to="/auth/login-oauth" to="/auth/login-oauth"
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600" className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600"
> >
Login with Address Login with Nostr Address
</Link> </Link>
<Link <Link
to="/auth/login-nsecbunker" to="/auth/login-nsecbunker"
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-neutral-50 rounded-xl bg-neutral-950 hover:bg-neutral-900" className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-neutral-50 rounded-xl bg-neutral-950 hover:bg-neutral-900"
> >
Login with nsecbunker Login with nsecBunker
</Link> </Link>
</div> </div>
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
@ -31,7 +29,7 @@ export function LoginScreen() {
</div> </div>
<div className="relative flex justify-center"> <div className="relative flex justify-center">
<span className="px-2 font-medium bg-black text-neutral-600"> <span className="px-2 font-medium bg-black text-neutral-600">
Or (Not recommended) Or continue with
</span> </span>
</div> </div>
</div> </div>
@ -43,8 +41,10 @@ export function LoginScreen() {
Login with Private Key Login with Private Key
</Link> </Link>
<p className="text-sm text-center text-neutral-500"> <p className="text-sm text-center text-neutral-500">
Lume will store your Private Key in{" "} Lume will put your Private Key in{" "}
<span className="text-teal-600">OS Secure Storage</span> <span className="text-teal-600">Secure Storage</span> depended
on your OS Platform. It will be secured by Password or Biometric
ID
</p> </p>
</div> </div>
</div> </div>

View File

@ -1,16 +1,6 @@
import { LoaderIcon } from "@lume/icons"; import { Link } from "react-router-dom";
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
export function WelcomeScreen() { export function WelcomeScreen() {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const gotoCreateAccount = () => {
setLoading(true);
navigate("/auth/create");
};
return ( return (
<div className="flex flex-col items-center justify-between w-full h-full"> <div className="flex flex-col items-center justify-between w-full h-full">
<div /> <div />
@ -29,17 +19,12 @@ export function WelcomeScreen() {
</p> </p>
</div> </div>
<div className="flex flex-col w-full max-w-xs gap-2 mx-auto"> <div className="flex flex-col w-full max-w-xs gap-2 mx-auto">
<button <Link
type="button" to="/auth/create"
onClick={gotoCreateAccount}
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600" className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-white bg-blue-500 rounded-xl hover:bg-blue-600"
> >
{loading ? ( Join Nostr
<LoaderIcon className="size-5 animate-spin" /> </Link>
) : (
"Create New Account"
)}
</button>
<Link <Link
to="/auth/login" to="/auth/login"
className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-neutral-50 rounded-xl bg-neutral-950 hover:bg-neutral-900" className="inline-flex items-center justify-center w-full h-12 text-lg font-medium text-neutral-50 rounded-xl bg-neutral-950 hover:bg-neutral-900"
@ -49,7 +34,7 @@ export function WelcomeScreen() {
</div> </div>
</div> </div>
<div className="flex items-center justify-center h-11"> <div className="flex items-center justify-center h-11">
<p className="text-neutral-800"> <p className="text-neutral-700">
Before joining Nostr, you can take time to learn more about Nostr{" "} Before joining Nostr, you can take time to learn more about Nostr{" "}
<Link <Link
to="https://nostr.com" to="https://nostr.com"