feat: add set up inbox relay screen
This commit is contained in:
@@ -48,9 +48,9 @@ pub async fn get_metadata(id: String, state: State<'_, Nostr>) -> Result<String,
|
||||
#[specta::specta]
|
||||
pub async fn create_account(
|
||||
name: String,
|
||||
picture: String,
|
||||
picture: Option<String>,
|
||||
state: State<'_, Nostr>,
|
||||
) -> Result<(), String> {
|
||||
) -> Result<String, String> {
|
||||
let client = &state.client;
|
||||
let keys = Keys::generate();
|
||||
let npub = keys.public_key().to_bech32().map_err(|e| e.to_string())?;
|
||||
@@ -66,11 +66,18 @@ pub async fn create_account(
|
||||
client.set_signer(Some(signer)).await;
|
||||
|
||||
// Update metadata
|
||||
let url = Url::parse(&picture).map_err(|e| e.to_string())?;
|
||||
let metadata = Metadata::new().display_name(name).picture(url);
|
||||
let url = match picture {
|
||||
Some(p) => Some(Url::parse(&p).map_err(|e| e.to_string())?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let metadata = match url {
|
||||
Some(picture) => Metadata::new().display_name(name).picture(picture),
|
||||
None => Metadata::new().display_name(name),
|
||||
};
|
||||
|
||||
match client.set_metadata(&metadata).await {
|
||||
Ok(_) => Ok(()),
|
||||
Ok(_) => Ok(npub),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -150,6 +157,51 @@ pub async fn get_contact_list(state: State<'_, Nostr>) -> Result<Vec<String>, ()
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn get_inbox(id: String, state: State<'_, Nostr>) -> Result<Vec<String>, String> {
|
||||
let client = &state.client;
|
||||
let public_key = PublicKey::parse(id).map_err(|e| e.to_string())?;
|
||||
let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1);
|
||||
|
||||
match client.get_events_of(vec![inbox], None).await {
|
||||
Ok(events) => {
|
||||
if let Some(event) = events.into_iter().next() {
|
||||
let urls = event
|
||||
.tags()
|
||||
.iter()
|
||||
.filter_map(|tag| {
|
||||
if let Some(TagStandard::Relay(relay)) = tag.as_standardized() {
|
||||
Some(relay.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(urls)
|
||||
} else {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn set_inbox(relays: Vec<String>, state: State<'_, Nostr>) -> Result<(), String> {
|
||||
let client = &state.client;
|
||||
|
||||
let tags = relays.into_iter().map(|t| Tag::custom(TagKind::Relay, vec![t])).collect::<Vec<_>>();
|
||||
let event = EventBuilder::new(Kind::Custom(10050), "", tags);
|
||||
|
||||
match client.send_event_builder(event).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn login(
|
||||
@@ -222,6 +274,8 @@ pub async fn login(
|
||||
|
||||
let mut inbox_relays = state.inbox_relays.lock().await;
|
||||
inbox_relays.insert(public_key, urls);
|
||||
} else {
|
||||
return Err("404".into());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ fn main() {
|
||||
get_contact_list,
|
||||
get_chats,
|
||||
get_chat_messages,
|
||||
get_inbox,
|
||||
set_inbox,
|
||||
connect_inbox,
|
||||
disconnect_inbox,
|
||||
send_message,
|
||||
|
||||
@@ -12,7 +12,7 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async createAccount(name: string, picture: string) : Promise<Result<null, string>> {
|
||||
async createAccount(name: string, picture: string | null) : Promise<Result<string, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("create_account", { name, picture }) };
|
||||
} catch (e) {
|
||||
@@ -71,6 +71,22 @@ try {
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async getInbox(id: string) : Promise<Result<string[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("get_inbox", { id }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async setInbox(relays: string[]) : Promise<Result<null, string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("set_inbox", { relays }) };
|
||||
} catch (e) {
|
||||
if(e instanceof Error) throw e;
|
||||
else return { status: "error", error: e as any };
|
||||
}
|
||||
},
|
||||
async connectInbox(id: string) : Promise<Result<string[], string>> {
|
||||
try {
|
||||
return { status: "ok", data: await TAURI_INVOKE("connect_inbox", { id }) };
|
||||
|
||||
20
src/components/back.tsx
Normal file
20
src/components/back.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { cn } from "@/commons";
|
||||
import { useRouter } from "@tanstack/react-router";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export function GoBack({
|
||||
children,
|
||||
className,
|
||||
}: { children: ReactNode | ReactNode[]; className?: string }) {
|
||||
const { history } = useRouter();
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => history.go(-1)}
|
||||
className={cn(className)}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ const NostrConnectLazyImport = createFileRoute('/nostr-connect')()
|
||||
const NewLazyImport = createFileRoute('/new')()
|
||||
const ImportKeyLazyImport = createFileRoute('/import-key')()
|
||||
const CreateAccountLazyImport = createFileRoute('/create-account')()
|
||||
const AccountRelaysLazyImport = createFileRoute('/$account/relays')()
|
||||
const AccountChatsLazyImport = createFileRoute('/$account/chats')()
|
||||
const AccountChatsNewLazyImport = createFileRoute('/$account/chats/new')()
|
||||
|
||||
@@ -53,7 +54,14 @@ const CreateAccountLazyRoute = CreateAccountLazyImport.update({
|
||||
const IndexRoute = IndexImport.update({
|
||||
path: '/',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
} as any).lazy(() => import('./routes/index.lazy').then((d) => d.Route))
|
||||
|
||||
const AccountRelaysLazyRoute = AccountRelaysLazyImport.update({
|
||||
path: '/$account/relays',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any).lazy(() =>
|
||||
import('./routes/$account.relays.lazy').then((d) => d.Route),
|
||||
)
|
||||
|
||||
const AccountChatsLazyRoute = AccountChatsLazyImport.update({
|
||||
path: '/$account/chats',
|
||||
@@ -136,6 +144,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AccountChatsLazyImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/$account/relays': {
|
||||
id: '/$account/relays'
|
||||
path: '/$account/relays'
|
||||
fullPath: '/$account/relays'
|
||||
preLoaderRoute: typeof AccountRelaysLazyImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/$account/chats/$id': {
|
||||
id: '/$account/chats/$id'
|
||||
path: '/$id'
|
||||
@@ -166,6 +181,7 @@ export const routeTree = rootRoute.addChildren({
|
||||
AccountChatsIdRoute,
|
||||
AccountChatsNewLazyRoute,
|
||||
}),
|
||||
AccountRelaysLazyRoute,
|
||||
})
|
||||
|
||||
/* prettier-ignore-end */
|
||||
@@ -182,7 +198,8 @@ export const routeTree = rootRoute.addChildren({
|
||||
"/new",
|
||||
"/nostr-connect",
|
||||
"/$account/contacts",
|
||||
"/$account/chats"
|
||||
"/$account/chats",
|
||||
"/$account/relays"
|
||||
]
|
||||
},
|
||||
"/": {
|
||||
@@ -210,6 +227,9 @@ export const routeTree = rootRoute.addChildren({
|
||||
"/$account/chats/new"
|
||||
]
|
||||
},
|
||||
"/$account/relays": {
|
||||
"filePath": "$account.relays.lazy.tsx"
|
||||
},
|
||||
"/$account/chats/$id": {
|
||||
"filePath": "$account.chats.$id.tsx",
|
||||
"parent": "/$account/chats"
|
||||
|
||||
@@ -2,7 +2,13 @@ import { commands } from "@/commands";
|
||||
import { ago, cn } from "@/commons";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { User } from "@/components/user";
|
||||
import { ArrowRight, CirclesFour, Plus, X } from "@phosphor-icons/react";
|
||||
import {
|
||||
ArrowRight,
|
||||
CaretDown,
|
||||
CirclesFour,
|
||||
Plus,
|
||||
X,
|
||||
} from "@phosphor-icons/react";
|
||||
import * as Dialog from "@radix-ui/react-dialog";
|
||||
import * as ScrollArea from "@radix-ui/react-scroll-area";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
@@ -48,7 +54,7 @@ function Header() {
|
||||
data-tauri-drag-region
|
||||
className={cn(
|
||||
"shrink-0 h-12 flex items-center justify-between",
|
||||
platform === "macos" ? "pl-24 pr-3.5" : "px-3.5",
|
||||
platform === "macos" ? "pl-[78px] pr-3.5" : "px-3.5",
|
||||
)}
|
||||
>
|
||||
<CurrentUser />
|
||||
@@ -153,6 +159,12 @@ function ChatList() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : !data?.length ? (
|
||||
<div className="p-2">
|
||||
<div className="px-2 h-12 w-full rounded-lg bg-black/5 dark:bg-white/5 flex items-center justify-center text-sm">
|
||||
No chats.
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => (
|
||||
<Link
|
||||
@@ -380,13 +392,14 @@ function CurrentUser() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={(e) => showContextMenu(e)}
|
||||
className="shrink-0 size-8 flex items-center justify-center rounded-full ring-1 ring-teal-500"
|
||||
className="h-8 inline-flex items-center gap-1.5"
|
||||
>
|
||||
<User.Provider pubkey={params.account}>
|
||||
<User.Root className="shrink-0">
|
||||
<User.Avatar className="size-7 rounded-full" />
|
||||
<User.Avatar className="size-8 rounded-full" />
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<CaretDown className="size-3 text-neutral-600 dark:text-neutral-400" />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { CoopIcon } from "@/icons/coop";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createLazyFileRoute("/$account/chats/new")({
|
||||
component: Screen,
|
||||
@@ -7,8 +7,14 @@ export const Route = createLazyFileRoute("/$account/chats/new")({
|
||||
|
||||
function Screen() {
|
||||
return (
|
||||
<div className="size-full flex items-center justify-center">
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="size-full flex flex-col gap-3 items-center justify-center"
|
||||
>
|
||||
<CoopIcon className="size-10 text-neutral-200 dark:text-neutral-800" />
|
||||
<h1 className="text-center font-bold text-neutral-300 dark:text-neutral-700">
|
||||
coop on nostr.
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
166
src/routes/$account.relays.lazy.tsx
Normal file
166
src/routes/$account.relays.lazy.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import { commands } from "@/commands";
|
||||
import { Frame } from "@/components/frame";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { Plus, X } from "@phosphor-icons/react";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useEffect, useState, useTransition } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/$account/relays")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const navigate = Route.useNavigate();
|
||||
const { account } = Route.useParams();
|
||||
|
||||
const [newRelay, setNewRelay] = useState("");
|
||||
const [relays, setRelays] = useState<string[]>([]);
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const add = () => {
|
||||
try {
|
||||
let url = newRelay;
|
||||
|
||||
if (relays.length >= 3) {
|
||||
return message("You should keep relay lists small (1 - 3 relays).", {
|
||||
kind: "info",
|
||||
});
|
||||
}
|
||||
|
||||
if (!url.startsWith("wss://")) {
|
||||
url = `wss://${url}`;
|
||||
}
|
||||
|
||||
// Validate URL
|
||||
const relay = new URL(url);
|
||||
|
||||
// Update
|
||||
setRelays((prev) => [...prev, relay.toString()]);
|
||||
setNewRelay("");
|
||||
} catch {
|
||||
message("URL is not valid.", { kind: "error" });
|
||||
}
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
startTransition(async () => {
|
||||
if (!relays.length) {
|
||||
await message("You need to add at least 1 relay", { kind: "info" });
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await commands.setInbox(relays);
|
||||
|
||||
if (res.status === "ok") {
|
||||
navigate({
|
||||
to: "/",
|
||||
params: { account },
|
||||
replace: true,
|
||||
});
|
||||
} else {
|
||||
await message(res.error, {
|
||||
title: "Inbox Relays",
|
||||
kind: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
async function getRelays() {
|
||||
const res = await commands.getInbox(account);
|
||||
|
||||
if (res.status === "ok") {
|
||||
setRelays((prev) => [...prev, ...res.data]);
|
||||
}
|
||||
}
|
||||
|
||||
getRelays();
|
||||
}, []);
|
||||
|
||||
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">Inbox Relays</h1>
|
||||
<p className="text-sm text-neutral-700 dark:text-neutral-300">
|
||||
Inbox Relay is used to receive message from others
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Frame
|
||||
className="flex flex-col gap-3 p-3 rounded-xl overflow-hidden"
|
||||
shadow
|
||||
>
|
||||
<div className="text-sm text-neutral-700 dark:text-neutral-300">
|
||||
<p className="mb-1.5">
|
||||
You need to set at least 1 inbox relay in order to receive
|
||||
message from others.
|
||||
</p>
|
||||
<p>
|
||||
If you don't know which relay to add, you can use{" "}
|
||||
<span
|
||||
onClick={() => setNewRelay("wss://auth.nostr1.com")}
|
||||
onKeyDown={() => setNewRelay("wss://auth.nostr1.com")}
|
||||
className="font-semibold"
|
||||
>
|
||||
auth.nostr1.com
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
name="relay"
|
||||
type="text"
|
||||
placeholder="ex: relay.nostr.net, ..."
|
||||
value={newRelay}
|
||||
onChange={(e) => setNewRelay(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") add();
|
||||
}}
|
||||
className="flex-1 px-3 rounded-lg h-9 bg-transparent border border-neutral-200 dark:border-neutral-800 focus:border-blue-500 focus:outline-none placeholder:text-neutral-400 dark:placeholder:text-neutral-600"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
onClick={() => add()}
|
||||
className="inline-flex items-center justify-center size-9 rounded-lg bg-neutral-100 hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||
>
|
||||
<Plus className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{relays.map((relay) => (
|
||||
<div
|
||||
key={relay}
|
||||
className="flex items-center justify-between h-9 px-2 rounded-lg bg-neutral-100 dark:bg-neutral-900"
|
||||
>
|
||||
<div className="text-sm font-medium">{relay}</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex items-center justify-center rounded-md size-7 text-neutral-700 dark:text-white/20 hover:bg-black/10 dark:hover:bg-white/10"
|
||||
>
|
||||
<X className="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Frame>
|
||||
<div className="flex flex-col items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submit()}
|
||||
disabled={isPending || !relays.length}
|
||||
className="inline-flex items-center justify-center w-full h-9 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>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { commands } from "@/commands";
|
||||
import { GoBack } from "@/components/back";
|
||||
import { Frame } from "@/components/frame";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
@@ -12,7 +13,7 @@ export const Route = createLazyFileRoute("/create-account")({
|
||||
function Screen() {
|
||||
const navigate = Route.useNavigate();
|
||||
|
||||
const [picture, setPicture] = useState("");
|
||||
const [picture, setPicture] = useState(null);
|
||||
const [name, setName] = useState("");
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
@@ -21,7 +22,11 @@ function Screen() {
|
||||
const res = await commands.createAccount(name, picture);
|
||||
|
||||
if (res.status === "ok") {
|
||||
navigate({ to: "/", replace: true });
|
||||
navigate({
|
||||
to: "/$account/relays",
|
||||
params: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
} else {
|
||||
await message(res.error, {
|
||||
title: "New Identity",
|
||||
@@ -36,9 +41,7 @@ function Screen() {
|
||||
<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>
|
||||
<h1 className="leading-tight text-xl font-semibold">New Identity</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Frame
|
||||
@@ -86,6 +89,9 @@ function Screen() {
|
||||
>
|
||||
{isPending ? <Spinner /> : "Continue"}
|
||||
</button>
|
||||
<GoBack className="mt-2 w-full text-sm text-neutral-600 dark:text-neutral-400 inline-flex items-center justify-center">
|
||||
Back
|
||||
</GoBack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { commands } from "@/commands";
|
||||
import { GoBack } from "@/components/back";
|
||||
import { Frame } from "@/components/frame";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { Link, createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { useState, useTransition } from "react";
|
||||
|
||||
@@ -94,6 +95,9 @@ function Screen() {
|
||||
>
|
||||
{isPending ? <Spinner /> : "Continue"}
|
||||
</button>
|
||||
<GoBack className="mt-2 w-full text-sm text-neutral-600 dark:text-neutral-400 inline-flex items-center justify-center">
|
||||
Back
|
||||
</GoBack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
109
src/routes/index.lazy.tsx
Normal file
109
src/routes/index.lazy.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { commands } from "@/commands";
|
||||
import { npub } from "@/commons";
|
||||
import { Frame } from "@/components/frame";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { User } from "@/components/user";
|
||||
import { Plus } from "@phosphor-icons/react";
|
||||
import { Link, createLazyFileRoute } from "@tanstack/react-router";
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
|
||||
export const Route = createLazyFileRoute("/")({
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const context = Route.useRouteContext();
|
||||
const navigate = Route.useNavigate();
|
||||
|
||||
const currentDate = useMemo(
|
||||
() =>
|
||||
new Date().toLocaleString("default", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [value, setValue] = useState("");
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const loginWith = async (npub: string) => {
|
||||
setValue(npub);
|
||||
startTransition(async () => {
|
||||
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({
|
||||
to: "/$account/chats/new",
|
||||
params: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
} else {
|
||||
if (res.error === "404") {
|
||||
navigate({
|
||||
to: "/$account/relays",
|
||||
params: { account: npub },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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">
|
||||
<h3 className="leading-tight text-neutral-700 dark:text-neutral-300">
|
||||
{currentDate}
|
||||
</h3>
|
||||
<h1 className="leading-tight text-xl font-semibold">Welcome back!</h1>
|
||||
</div>
|
||||
<Frame
|
||||
className="flex flex-col w-full divide-y divide-neutral-100 dark:divide-white/5 rounded-xl overflow-hidden"
|
||||
shadow
|
||||
>
|
||||
{context.accounts.map((account) => (
|
||||
<div
|
||||
key={account}
|
||||
onClick={() => loginWith(account)}
|
||||
onKeyDown={() => loginWith(account)}
|
||||
className="flex items-center justify-between hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<User.Provider pubkey={account}>
|
||||
<User.Root className="flex items-center gap-2.5 p-3">
|
||||
<User.Avatar className="rounded-full size-10" />
|
||||
<div className="inline-flex flex-col items-start">
|
||||
<User.Name className="max-w-[6rem] truncate font-medium leading-tight" />
|
||||
<span className="text-sm text-neutral-700 dark:text-neutral-300">
|
||||
{npub(account, 16)}
|
||||
</span>
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<div className="inline-flex items-center justify-center size-10">
|
||||
{value === account && isPending ? <Spinner /> : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Link
|
||||
to="/new"
|
||||
className="flex items-center justify-between hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<div className="flex items-center gap-2.5 p-3">
|
||||
<div className="inline-flex items-center justify-center rounded-full size-10 bg-neutral-200 dark:bg-white/10">
|
||||
<Plus className="size-5" />
|
||||
</div>
|
||||
<span className="truncate text-sm font-medium leading-tight">
|
||||
Add an account
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</Frame>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
import { commands } from "@/commands";
|
||||
import { npub } from "@/commons";
|
||||
import { Frame } from "@/components/frame";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { User } from "@/components/user";
|
||||
import { Plus } from "@phosphor-icons/react";
|
||||
import { Link, createFileRoute, redirect } from "@tanstack/react-router";
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createFileRoute("/")({
|
||||
beforeLoad: async () => {
|
||||
@@ -20,94 +14,4 @@ export const Route = createFileRoute("/")({
|
||||
|
||||
return { accounts };
|
||||
},
|
||||
component: Screen,
|
||||
});
|
||||
|
||||
function Screen() {
|
||||
const context = Route.useRouteContext();
|
||||
const navigate = Route.useNavigate();
|
||||
|
||||
const currentDate = useMemo(
|
||||
() =>
|
||||
new Date().toLocaleString("default", {
|
||||
weekday: "long",
|
||||
month: "long",
|
||||
day: "numeric",
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const [value, setValue] = useState("");
|
||||
const [isPending, startTransition] = useTransition();
|
||||
|
||||
const loginWith = async (npub: string) => {
|
||||
setValue(npub);
|
||||
startTransition(async () => {
|
||||
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({
|
||||
to: "/$account/chats/new",
|
||||
params: { account: res.data },
|
||||
replace: true,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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">
|
||||
<h3 className="leading-tight text-neutral-700 dark:text-neutral-300">
|
||||
{currentDate}
|
||||
</h3>
|
||||
<h1 className="leading-tight text-xl font-semibold">Welcome back!</h1>
|
||||
</div>
|
||||
<Frame
|
||||
className="flex flex-col w-full divide-y divide-neutral-100 dark:divide-white/5 rounded-xl overflow-hidden"
|
||||
shadow
|
||||
>
|
||||
{context.accounts.map((account) => (
|
||||
<div
|
||||
key={account}
|
||||
onClick={() => loginWith(account)}
|
||||
onKeyDown={() => loginWith(account)}
|
||||
className="flex items-center justify-between hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<User.Provider pubkey={account}>
|
||||
<User.Root className="flex items-center gap-2.5 p-3">
|
||||
<User.Avatar className="rounded-full size-10" />
|
||||
<div className="inline-flex flex-col items-start">
|
||||
<User.Name className="max-w-[6rem] truncate font-medium leading-tight" />
|
||||
<span className="text-sm text-neutral-700 dark:text-neutral-300">
|
||||
{npub(account, 16)}
|
||||
</span>
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
<div className="inline-flex items-center justify-center size-10">
|
||||
{value === account && isPending ? <Spinner /> : null}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<Link
|
||||
to="/new"
|
||||
className="flex items-center justify-between hover:bg-black/5 dark:hover:bg-white/5"
|
||||
>
|
||||
<div className="flex items-center gap-2.5 p-3">
|
||||
<div className="inline-flex items-center justify-center rounded-full size-10 bg-neutral-200 dark:bg-white/10">
|
||||
<Plus className="size-5" />
|
||||
</div>
|
||||
<span className="truncate text-sm font-medium leading-tight">
|
||||
Add an account
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
</Frame>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createLazyFileRoute, Link } from "@tanstack/react-router";
|
||||
import { Link, createLazyFileRoute } from "@tanstack/react-router";
|
||||
|
||||
export const Route = createLazyFileRoute("/new")({
|
||||
component: Screen,
|
||||
@@ -16,13 +16,13 @@ function Screen() {
|
||||
<div className="flex flex-col gap-3">
|
||||
<Link
|
||||
to="/create-account"
|
||||
className="w-full h-9 bg-blue-500 hover:bg-blue-600 text-white rounded-lg inline-flex items-center justify-center shadow"
|
||||
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-9 bg-white hover:bg-neutral-100 dark:hover:bg-neutral-950 dark:bg-neutral-900 rounded-lg inline-flex items-center justify-center"
|
||||
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>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { commands } from "@/commands";
|
||||
import { GoBack } from "@/components/back";
|
||||
import { Frame } from "@/components/frame";
|
||||
import { Spinner } from "@/components/spinner";
|
||||
import { createLazyFileRoute } from "@tanstack/react-router";
|
||||
@@ -47,9 +48,7 @@ function Screen() {
|
||||
<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>
|
||||
<h1 className="leading-tight text-xl font-semibold">Nostr Connect</h1>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Frame
|
||||
@@ -85,6 +84,9 @@ function Screen() {
|
||||
Waiting confirmation...
|
||||
</p>
|
||||
) : null}
|
||||
<GoBack className="mt-2 w-full text-sm text-neutral-600 dark:text-neutral-400 inline-flex items-center justify-center">
|
||||
Back
|
||||
</GoBack>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user