feat: account switcher

This commit is contained in:
reya 2024-02-24 14:41:57 +07:00
parent 84584a4d1f
commit 88a6c3c81f
14 changed files with 114 additions and 34 deletions

View File

@ -1,29 +1,65 @@
import { useArk } from "@lume/ark"; import { useArk } from "@lume/ark";
import { PlusIcon } from "@lume/icons";
import { Account } from "@lume/types";
import { User } from "@lume/ui"; import { User } from "@lume/ui";
import { Link, useNavigate, useParams } from "@tanstack/react-router";
import { useEffect, useState } from "react";
export function Accounts() { export function Accounts() {
const ark = useArk(); const ark = useArk();
const params = useParams({ strict: false });
const [accounts, setAccounts] = useState<Account[]>(null);
useEffect(() => {
async function getAllAccounts() {
const data = await ark.get_all_accounts();
if (data) setAccounts(data);
}
getAllAccounts();
}, []);
return ( return (
<div data-tauri-drag-region className="flex items-center gap-4"> <div data-tauri-drag-region className="flex items-center gap-4">
{ark.accounts.map((account) => <Link
account.npub === ark.account.npub ? ( to="/landing"
<Active pubkey={account.npub} /> className="inline-flex size-7 items-center justify-center rounded-full bg-neutral-300 ring-offset-2 ring-offset-neutral-200 hover:ring-1 hover:ring-blue-500 dark:bg-neutral-700 dark:ring-offset-neutral-950"
) : ( >
<Inactive pubkey={ark.account.npub} /> <PlusIcon className="size-4" />
), </Link>
)} {accounts
? accounts.map((account) =>
// @ts-ignore, useless
account.npub === params.account ? (
<Active key={account.npub} pubkey={account.npub} />
) : (
<Inactive key={account.npub} pubkey={account.npub} />
),
)
: null}
</div> </div>
); );
} }
function Inactive({ pubkey }: { pubkey: string }) { function Inactive({ pubkey }: { pubkey: string }) {
const ark = useArk();
const navigate = useNavigate();
const changeAccount = async (npub: string) => {
const select = await ark.load_selected_account(npub);
if (select)
navigate({ to: "/$account/home/local", params: { account: npub } });
};
return ( return (
<User.Provider pubkey={pubkey}> <button type="button" onClick={() => changeAccount(pubkey)}>
<User.Root className="rounded-full ring-offset-2 ring-offset-neutral-200 hover:ring-1 hover:ring-blue-500 dark:ring-offset-neutral-950"> <User.Provider pubkey={pubkey}>
<User.Avatar className="aspect-square h-auto w-7 rounded-full object-cover" /> <User.Root className="rounded-full ring-offset-2 ring-offset-neutral-200 hover:ring-1 hover:ring-blue-500 dark:ring-offset-neutral-950">
</User.Root> <User.Avatar className="aspect-square h-auto w-7 rounded-full object-cover" />
</User.Provider> </User.Root>
</User.Provider>
</button>
); );
} }

View File

@ -7,13 +7,13 @@ import {
SpaceFilledIcon, SpaceFilledIcon,
SpaceIcon, SpaceIcon,
} from "@lume/icons"; } from "@lume/icons";
import { Link } from "@tanstack/react-router"; import { Link, useParams } from "@tanstack/react-router";
import { Outlet, createFileRoute } from "@tanstack/react-router"; import { Outlet, createFileRoute } from "@tanstack/react-router";
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { Accounts } from "@/components/accounts"; import { Accounts } from "@/components/accounts";
import { useArk } from "@lume/ark"; import { useArk } from "@lume/ark";
export const Route = createFileRoute("/app")({ export const Route = createFileRoute("/$account")({
component: App, component: App,
}); });
@ -53,12 +53,15 @@ function App() {
} }
function Navigation() { function Navigation() {
// @ts-ignore, useless
const { account } = useParams({ strict: false });
return ( return (
<div <div
data-tauri-drag-region data-tauri-drag-region
className="flex h-full flex-1 items-center gap-2" className="flex h-full flex-1 items-center gap-2"
> >
<Link to="/app/home/local"> <Link to="/$account/home/local" params={{ account }}>
{({ isActive }) => ( {({ isActive }) => (
<div <div
className={cn( className={cn(
@ -75,7 +78,7 @@ function Navigation() {
</div> </div>
)} )}
</Link> </Link>
<Link to="/app/space"> <Link to="/$account/space" params={{ account }}>
{({ isActive }) => ( {({ isActive }) => (
<div <div
className={cn( className={cn(
@ -92,7 +95,7 @@ function Navigation() {
</div> </div>
)} )}
</Link> </Link>
<Link to="/app/activity"> <Link to="/$account/activity" params={{ account }}>
{({ isActive }) => ( {({ isActive }) => (
<div <div
className={cn( className={cn(

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/app/activity")({ export const Route = createLazyFileRoute("/$account/activity")({
component: Activity, component: Activity,
}); });

View File

@ -1,18 +1,29 @@
import { cn } from "@lume/utils"; import { cn } from "@lume/utils";
import { Link } from "@tanstack/react-router"; import {
import { Outlet, createFileRoute } from "@tanstack/react-router"; Outlet,
Link,
createFileRoute,
useParams,
} from "@tanstack/react-router";
export const Route = createFileRoute("/app/home")({ export const Route = createFileRoute("/$account/home")({
component: Home, component: Home,
}); });
function Home() { function Home() {
// @ts-ignore, useless
const { account } = useParams({ strict: false });
return ( return (
<div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5"> <div className="h-full w-full overflow-hidden overflow-y-auto rounded-xl bg-white shadow-[rgba(50,_50,_105,_0.15)_0px_2px_5px_0px,_rgba(0,_0,_0,_0.05)_0px_1px_1px_0px] dark:bg-black dark:shadow-none dark:ring-1 dark:ring-white/5">
<div className="mx-auto flex w-full max-w-xl flex-col"> <div className="mx-auto flex w-full max-w-xl flex-col">
<div className="mx-auto flex h-28 w-1/2 items-center"> <div className="mx-auto flex h-28 w-1/2 items-center">
<div className="flex h-11 w-full flex-1 items-center rounded-full bg-neutral-100 dark:bg-neutral-900"> <div className="flex h-11 w-full flex-1 items-center rounded-full bg-neutral-100 dark:bg-neutral-900">
<Link to="/app/home/local" className="h-11 flex-1 p-1"> <Link
to="/$account/home/local"
params={{ account }}
className="h-11 flex-1 p-1"
>
{({ isActive }) => ( {({ isActive }) => (
<div <div
className={cn( className={cn(
@ -26,7 +37,11 @@ function Home() {
</div> </div>
)} )}
</Link> </Link>
<Link to="/app/home/global" className="h-11 flex-1 p-1"> <Link
to="/$account/home/global"
params={{ account }}
className="h-11 flex-1 p-1"
>
{({ isActive }) => ( {({ isActive }) => (
<div <div
className={cn( className={cn(

View File

@ -9,7 +9,7 @@ import { Virtualizer } from "virtua";
import { TextNote } from "./-components/text"; import { TextNote } from "./-components/text";
import { RepostNote } from "./-components/repost"; import { RepostNote } from "./-components/repost";
export const Route = createLazyFileRoute("/app/home/global")({ export const Route = createLazyFileRoute("/$account/home/global")({
component: GlobalTimeline, component: GlobalTimeline,
}); });

View File

@ -9,15 +9,17 @@ import { Virtualizer } from "virtua";
import { TextNote } from "./-components/text"; import { TextNote } from "./-components/text";
import { RepostNote } from "./-components/repost"; import { RepostNote } from "./-components/repost";
export const Route = createLazyFileRoute("/app/home/local")({ export const Route = createLazyFileRoute("/$account/home/local")({
component: LocalTimeline, component: LocalTimeline,
}); });
function LocalTimeline() { function LocalTimeline() {
const ark = useArk(); const ark = useArk();
const { account } = Route.useParams();
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } = const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({ useInfiniteQuery({
queryKey: ["events", "local"], queryKey: ["newsfeed", account],
initialPageParam: 0, initialPageParam: 0,
queryFn: async ({ pageParam }: { pageParam: number }) => { queryFn: async ({ pageParam }: { pageParam: number }) => {
const events = await ark.get_events( const events = await ark.get_events(
@ -38,6 +40,7 @@ function LocalTimeline() {
}); });
const renderItem = (event: Event) => { const renderItem = (event: Event) => {
if (!event) return;
switch (event.kind) { switch (event.kind) {
case Kind.Repost: case Kind.Repost:
return <RepostNote key={event.id} event={event} />; return <RepostNote key={event.id} event={event} />;

View File

@ -1,6 +1,6 @@
import { createLazyFileRoute } from "@tanstack/react-router"; import { createLazyFileRoute } from "@tanstack/react-router";
export const Route = createLazyFileRoute("/app/space")({ export const Route = createLazyFileRoute("/$account/space")({
component: Space, component: Space,
}); });

View File

@ -26,7 +26,8 @@ function Create() {
try { try {
await ark.save_account(keys); await ark.save_account(keys);
navigate({ navigate({
to: "/app/home/local", to: "/$account/home/local",
params: { account: keys.npub },
search: { onboarding: true }, search: { onboarding: true },
replace: true, replace: true,
}); });

View File

@ -32,7 +32,8 @@ function Import() {
nsec: key, nsec: key,
}); });
navigate({ navigate({
to: "/app/home", to: "/$account/home/local",
params: { account: npub },
search: { onboarding: true }, search: { onboarding: true },
replace: true, replace: true,
}); });

View File

@ -20,10 +20,12 @@ export const Route = createFileRoute("/")({
}); });
// Only 1 account, skip account selection screen // Only 1 account, skip account selection screen
case 1: case 1:
const loadAccount = await ark.load_selected_account(accounts[0].npub); const account = accounts[0].npub;
const loadAccount = await ark.load_selected_account(account);
if (loadAccount) { if (loadAccount) {
throw redirect({ throw redirect({
to: "/app/home/local", to: "/$account/home/local",
params: { account },
search: { search: {
redirect: location.href, redirect: location.href,
}, },
@ -48,7 +50,8 @@ function Screen() {
const loadAccount = await ark.load_selected_account(npub); const loadAccount = await ark.load_selected_account(npub);
if (loadAccount) { if (loadAccount) {
navigate({ navigate({
to: "/app/home", to: "/$account/home/local",
params: { account: npub },
replace: true, replace: true,
}); });
} }

View File

@ -47,3 +47,18 @@ default = ["custom-protocol"]
# this feature is used used for production builds where `devPath` points to the filesystem # this feature is used used for production builds where `devPath` points to the filesystem
# DO NOT remove this # DO NOT remove this
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]
[profile.dev.package."*"]
opt-level = 3
[profile.release]
codegen-units = 1
lto = true
panic = "abort"
incremental = false
opt-level = 3
strip = true
rpath = false
debug = false
debug-assertions = false
overflow-checks = false

View File

@ -26,8 +26,11 @@ pub async fn get_profile(id: &str, state: State<'_, Nostr>) -> Result<Metadata,
if let Ok(events) = query { if let Ok(events) = query {
if let Some(event) = events.first() { if let Some(event) = events.first() {
let metadata: Metadata = Metadata::from_json(&event.content).unwrap(); if let Ok(metadata) = Metadata::from_json(&event.content) {
Ok(metadata) Ok(metadata)
} else {
Err("Parse metadata failed".into())
}
} else { } else {
let rand_metadata = Metadata::new(); let rand_metadata = Metadata::new();
Ok(rand_metadata) Ok(rand_metadata)