feat: add auth screens

This commit is contained in:
reya
2024-07-25 10:59:36 +07:00
parent d206f1d2aa
commit 005cbeab72
12 changed files with 571 additions and 69 deletions

View File

@@ -4,7 +4,7 @@ import { Spinner } from "@/components/spinner";
import { ArrowUp, CloudArrowUp, Paperclip } from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { createFileRoute } from "@tanstack/react-router";
import { createLazyFileRoute } from "@tanstack/react-router";
import { listen } from "@tauri-apps/api/event";
import { message } from "@tauri-apps/plugin-dialog";
import type { NostrEvent } from "nostr-tools";
@@ -17,7 +17,7 @@ type Payload = {
sender: string;
};
export const Route = createFileRoute("/$account/chats/$id")({
export const Route = createLazyFileRoute("/$account/chats/$id")({
component: Screen,
});

View File

@@ -4,7 +4,7 @@ import { User } from "@/components/user";
import { Plus, UsersThree } from "@phosphor-icons/react";
import * as ScrollArea from "@radix-ui/react-scroll-area";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Link, Outlet, createFileRoute } from "@tanstack/react-router";
import { Link, Outlet, createLazyFileRoute } from "@tanstack/react-router";
import { listen } from "@tauri-apps/api/event";
import type { NostrEvent } from "nostr-tools";
import { useEffect } from "react";
@@ -14,7 +14,7 @@ type Payload = {
sender: string;
};
export const Route = createFileRoute("/$account/chats")({
export const Route = createLazyFileRoute("/$account/chats")({
component: Screen,
});

View File

@@ -0,0 +1,94 @@
import { commands } from "@/commands";
import { Frame } from "@/components/frame";
import { Spinner } from "@/components/spinner";
import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState, useTransition } from "react";
export const Route = createLazyFileRoute("/create-account")({
component: Screen,
});
function Screen() {
const navigate = Route.useNavigate();
const [picture, setPicture] = useState("");
const [name, setName] = useState("");
const [isPending, startTransition] = useTransition();
const submit = async () => {
startTransition(async () => {
const res = await commands.createAccount(name, picture);
if (res.status === "ok") {
navigate({ to: "/", replace: true });
} else {
await message(res.error, {
title: "New Identity",
kind: "error",
});
return;
}
});
};
return (
<div className="size-full flex items-center justify-center">
<div className="w-[320px] flex flex-col gap-8">
<div className="flex flex-col gap-1 text-center">
<h1 className="leading-tight text-xl font-semibold">
Import Private Key
</h1>
</div>
<div className="flex flex-col gap-3">
<Frame
className="flex flex-col gap-3 p-3 rounded-xl overflow-hidden"
shadow
>
<div className="flex flex-col gap-1">
<label
htmlFor="avatar"
className="font-medium text-neutral-900 dark:text-neutral-100"
>
Avatar
</label>
<input
name="avatar"
type="text"
placeholder="https://"
value={picture}
onChange={(e) => setPicture(e.target.value)}
className="px-3 rounded-lg h-10 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none"
/>
</div>
<div className="flex flex-col gap-1">
<label
htmlFor="name"
className="font-medium text-neutral-900 dark:text-neutral-100"
>
Name
</label>
<input
name="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="px-3 rounded-lg h-10 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none"
/>
</div>
</Frame>
<div className="flex flex-col items-center gap-1">
<button
type="button"
onClick={() => submit()}
disabled={isPending}
className="inline-flex items-center justify-center w-full h-10 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
>
{isPending ? <Spinner /> : "Continue"}
</button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,102 @@
import { commands } from "@/commands";
import { Frame } from "@/components/frame";
import { Spinner } from "@/components/spinner";
import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState, useTransition } from "react";
export const Route = createLazyFileRoute("/import-key")({
component: Screen,
});
function Screen() {
const navigate = Route.useNavigate();
const [key, setKey] = useState("");
const [password, setPassword] = useState("");
const [isPending, startTransition] = useTransition();
const submit = async () => {
startTransition(async () => {
if (!key.startsWith("nsec1")) {
await message(
"You need to enter a valid private key starts with nsec or ncryptsec",
{ title: "Import Key", kind: "info" },
);
return;
}
const res = await commands.importKey(key, password);
if (res.status === "ok") {
navigate({ to: "/", replace: true });
} else {
await message(res.error, {
title: "Import Private Ket",
kind: "error",
});
return;
}
});
};
return (
<div className="size-full flex items-center justify-center">
<div className="w-[320px] flex flex-col gap-8">
<div className="flex flex-col gap-1 text-center">
<h1 className="leading-tight text-xl font-semibold">
Import Private Key
</h1>
</div>
<div className="flex flex-col gap-3">
<Frame
className="flex flex-col gap-3 p-3 rounded-xl overflow-hidden"
shadow
>
<div className="flex flex-col gap-1">
<label
htmlFor="key"
className="font-medium text-neutral-900 dark:text-neutral-100"
>
Private Key
</label>
<input
name="key"
type="text"
placeholder="nsec or ncryptsec..."
value={key}
onChange={(e) => setKey(e.target.value)}
className="px-3 rounded-lg h-10 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none"
/>
</div>
<div className="flex flex-col gap-1">
<label
htmlFor="password"
className="font-medium text-neutral-900 dark:text-neutral-100"
>
Password (Optional)
</label>
<input
name="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="px-3 rounded-lg h-10 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none"
/>
</div>
</Frame>
<div className="flex flex-col items-center gap-1">
<button
type="button"
onClick={() => submit()}
disabled={isPending}
className="inline-flex items-center justify-center w-full h-10 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
>
{isPending ? <Spinner /> : "Continue"}
</button>
</div>
</div>
</div>
</div>
);
}

View File

@@ -43,7 +43,9 @@ function Screen() {
const loginWith = async (npub: string) => {
setValue(npub);
startTransition(async () => {
const res = await commands.login(npub);
const bunker: string = localStorage.getItem(`${npub}_bunker`);
const verifyBunker = bunker?.length && bunker?.startsWith("bunker://");
const res = await commands.login(npub, verifyBunker ? bunker : null);
if (res.status === "ok") {
navigate({

View File

@@ -1,5 +1,39 @@
import { createLazyFileRoute } from '@tanstack/react-router'
import { createLazyFileRoute, Link } from "@tanstack/react-router";
export const Route = createLazyFileRoute('/new')({
component: () => <div>Hello /new!</div>
})
export const Route = createLazyFileRoute("/new")({
component: Screen,
});
function Screen() {
return (
<div className="size-full flex items-center justify-center">
<div className="w-[320px] flex flex-col gap-8">
<div className="flex flex-col gap-1 text-center">
<h1 className="leading-tight text-xl font-semibold">
Direct Message client for Nostr.
</h1>
</div>
<div className="flex flex-col gap-3">
<Link
to="/create-account"
className="w-full h-10 bg-blue-500 hover:bg-blue-600 text-white rounded-lg inline-flex items-center justify-center shadow"
>
Create a new identity
</Link>
<Link
to="/nostr-connect"
className="w-full h-10 bg-white hover:bg-neutral-100 dark:hover:bg-neutral-950 dark:bg-neutral-900 rounded-lg inline-flex items-center justify-center"
>
Login with Nostr Connect
</Link>
<Link
to="/import-key"
className="w-full text-sm text-neutral-600 dark:text-neutral-400 inline-flex items-center justify-center"
>
Login with Private Key (not recommended)
</Link>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,93 @@
import { commands } from "@/commands";
import { Frame } from "@/components/frame";
import { Spinner } from "@/components/spinner";
import { createLazyFileRoute } from "@tanstack/react-router";
import { message } from "@tauri-apps/plugin-dialog";
import { useState, useTransition } from "react";
export const Route = createLazyFileRoute("/nostr-connect")({
component: Screen,
});
function Screen() {
const navigate = Route.useNavigate();
const [uri, setUri] = useState("");
const [isPending, startTransition] = useTransition();
const submit = async () => {
startTransition(async () => {
if (!uri.startsWith("bunker://")) {
await message(
"You need to enter a valid Connect URI starts with bunker://",
{ title: "Nostr Connect", kind: "info" },
);
return;
}
const res = await commands.connectAccount(uri);
if (res.status === "ok") {
const npub = res.data;
const parsed = new URL(uri);
parsed.searchParams.delete("secret");
// save connection string
localStorage.setItem(`${npub}_bunker`, parsed.toString());
navigate({ to: "/", replace: true });
} else {
await message(res.error, { title: "Nostr Connect", kind: "error" });
return;
}
});
};
return (
<div className="size-full flex items-center justify-center">
<div className="w-[320px] flex flex-col gap-8">
<div className="flex flex-col gap-1 text-center">
<h1 className="leading-tight text-xl font-semibold">
Nostr Connect.
</h1>
</div>
<div className="flex flex-col gap-3">
<Frame
className="flex flex-col gap-1 p-3 rounded-xl overflow-hidden"
shadow
>
<label
htmlFor="uri"
className="font-medium text-neutral-900 dark:text-neutral-100"
>
Connection String
</label>
<input
name="uri"
type="text"
placeholder="bunker://..."
value={uri}
onChange={(e) => setUri(e.target.value)}
className="px-3 rounded-lg h-10 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none"
/>
</Frame>
<div className="flex flex-col items-center gap-1">
<button
type="button"
onClick={() => submit()}
disabled={isPending}
className="inline-flex items-center justify-center w-full h-10 text-sm font-semibold text-white bg-blue-500 rounded-lg shrink-0 hover:bg-blue-600 disabled:opacity-50"
>
{isPending ? <Spinner /> : "Continue"}
</button>
{isPending ? (
<p className="text-sm text-center text-neutral-600 dark:text-neutral-400">
Waiting confirmation...
</p>
) : null}
</div>
</div>
</div>
</div>
);
}