chore: clean up

This commit is contained in:
reya 2024-04-03 07:29:46 +07:00
parent 89bb8d88f6
commit 763cb10e85
42 changed files with 104 additions and 532 deletions

View File

@ -1,5 +1,3 @@
import { useArk } from "@lume/ark";
import { ArkProvider } from "./ark";
import { QueryClient } from "@tanstack/react-query";
import { RouterProvider, createRouter } from "@tanstack/react-router";
import React, { StrictMode } from "react";
@ -13,6 +11,7 @@ import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client
import { createSyncStoragePersister } from "@tanstack/query-sync-storage-persister";
import { routeTree } from "./router.gen"; // auto generated file
import { CancelCircleIcon, CheckCircleIcon, InfoCircleIcon } from "@lume/icons";
import { Ark } from "@lume/ark";
const queryClient = new QueryClient({
defaultOptions: {
@ -27,6 +26,7 @@ const persister = createSyncStoragePersister({
storage: window.localStorage,
});
const ark = new Ark();
const platformName = await platform();
const osLocale = (await locale()).slice(0, 2);
@ -34,10 +34,10 @@ const osLocale = (await locale()).slice(0, 2);
const router = createRouter({
routeTree,
context: {
ark: undefined!,
platform: platformName,
locale: osLocale,
settings: null,
ark,
queryClient,
},
});
@ -49,17 +49,8 @@ declare module "@tanstack/react-router" {
}
}
function InnerApp() {
const ark = useArk();
return <RouterProvider router={router} context={{ ark }} />;
}
function App() {
return (
<ArkProvider>
<InnerApp />
</ArkProvider>
);
return <RouterProvider router={router} />;
}
// biome-ignore lint/style/noNonNullAssertion: idk

View File

@ -1,7 +0,0 @@
import { Ark, ArkContext } from "@lume/ark";
import { PropsWithChildren, useMemo } from "react";
export const ArkProvider = ({ children }: PropsWithChildren<object>) => {
const ark = useMemo(() => new Ark(), []);
return <ArkContext.Provider value={ark}>{children}</ArkContext.Provider>;
};

View File

@ -1,11 +1,14 @@
import { useArk } from "@lume/ark";
import { Account } from "@lume/types";
import { User } from "@lume/ui";
import { useNavigate, useParams } from "@tanstack/react-router";
import {
useNavigate,
useParams,
useRouteContext,
} from "@tanstack/react-router";
import { useEffect, useState } from "react";
export function Accounts() {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const params = useParams({ strict: false });
const [accounts, setAccounts] = useState<Account[]>(null);
@ -36,7 +39,7 @@ export function Accounts() {
}
function Inactive({ pubkey }: { pubkey: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const navigate = useNavigate();
const changeAccount = async (npub: string) => {

View File

@ -1,6 +1,6 @@
import { useArk } from "@lume/ark";
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import { Dispatch, ReactNode, SetStateAction, useState } from "react";
import { toast } from "sonner";
@ -13,7 +13,7 @@ export function AvatarUploader({
children: ReactNode;
className?: string;
}) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const [loading, setLoading] = useState(false);
const uploadAvatar = async () => {

View File

@ -1,121 +0,0 @@
import { ArrowRightIcon, CancelIcon } from "@lume/icons";
import * as Dialog from "@radix-ui/react-dialog";
import { Link, useParams } from "@tanstack/react-router";
import { invoke } from "@tauri-apps/api/core";
import { useState } from "react";
import { toast } from "sonner";
export function BackupDialog() {
// @ts-ignore, magic!!!
const { account } = useParams({ strict: false });
const [key, setKey] = useState(null);
const [passphase, setPassphase] = useState("");
const [loading, setLoading] = useState(false);
const encryptKey = async () => {
try {
setLoading(true);
const encrypted: string = await invoke("get_encrypted_key", {
npub: account,
password: passphase,
});
if (encrypted) {
setKey(encrypted);
}
setLoading(false);
} catch (e) {
setLoading(false);
toast.error(String(e));
}
};
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button
type="button"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
>
Claim & Backup
</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur dark:bg-white/30" />
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Dialog.Close className="absolute right-5 top-5 flex w-12 flex-col items-center justify-center gap-1 text-white">
<CancelIcon className="size-8" />
<span className="text-sm font-medium uppercase text-neutral-400 dark:text-neutral-600">
Esc
</span>
</Dialog.Close>
<div className="relative flex h-min w-full max-w-xl flex-col gap-8 rounded-xl bg-white p-5 dark:bg-black">
<div className="flex flex-col">
<h3 className="text-lg font-semibold">
This is your account key
</h3>
<p>
It's use for login to Lume or other Nostr clients. You will lost
access to your account if you lose this key.
</p>
</div>
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<label htmlFor="nsec">Set a passphase to secure your key</label>
<div className="relative">
<input
name="passphase"
type="password"
value={passphase}
onChange={(e) => setPassphase(e.target.value)}
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
</div>
{key ? (
<div className="flex flex-col gap-2">
<label htmlFor="nsec">
Copy this key and keep it in safe place
</label>
<input
name="nsec"
type="text"
value={key}
readOnly
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
) : null}
</div>
<div className="flex flex-col gap-3">
{!key ? (
<button
type="button"
onClick={encryptKey}
disabled={loading}
className="inline-flex h-11 w-full items-center justify-between gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
>
<div className="size-5" />
<div>Submit</div>
<ArrowRightIcon className="size-5" />
</button>
) : (
<Link
to="/$account/home/local"
params={{ account }}
search={{ guest: false }}
className="inline-flex h-11 w-full items-center justify-center gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
>
I've safely store my account key
</Link>
)}
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@ -1,12 +1,11 @@
import { useArk } from "@lume/ark";
import { User } from "@lume/ui";
import { getBitcoinDisplayValues } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
import { useEffect, useMemo, useState } from "react";
export function Balance({ account }: { account: string }) {
const { ark } = useRouteContext({ strict: false });
const [balance, setBalance] = useState(0);
const ark = useArk();
const value = useMemo(() => getBitcoinDisplayValues(balance), [balance]);
useEffect(() => {

View File

@ -1,125 +0,0 @@
import { useArk } from "@lume/ark";
import { ArrowRightIcon, CancelIcon } from "@lume/icons";
import * as Dialog from "@radix-ui/react-dialog";
import { useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { toast } from "sonner";
export function LoginDialog() {
const ark = useArk();
const navigate = useNavigate();
const [nsec, setNsec] = useState("");
const [passphase, setPassphase] = useState("");
const login = async () => {
try {
if (!nsec.length) {
return toast.info("You must enter a valid nsec or ncrypto");
}
if (nsec.startsWith("ncrypto") && !passphase.length) {
return toast.warning("You must provide a passphase for ncrypto key");
}
const save = await ark.save_account(nsec, passphase);
if (save) {
navigate({ to: "/", search: { guest: false } });
}
} catch (e) {
toast.error(String(e));
}
};
return (
<Dialog.Root>
<Dialog.Trigger asChild>
<button
type="button"
className="inline-flex h-9 w-full items-center justify-center rounded-lg bg-neutral-900 text-sm font-medium leading-tight text-neutral-100 hover:bg-neutral-800"
>
Add account
</button>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/30 backdrop-blur dark:bg-white/30" />
<Dialog.Content className="fixed inset-0 z-50 flex min-h-full items-center justify-center">
<Dialog.Close className="absolute right-5 top-5 flex w-12 flex-col items-center justify-center gap-1 text-white">
<CancelIcon className="size-8" />
<span className="text-sm font-medium uppercase text-neutral-400 dark:text-neutral-600">
Esc
</span>
</Dialog.Close>
<div className="relative flex h-min w-full max-w-xl flex-col gap-8 rounded-xl bg-white p-5 dark:bg-black">
<div className="flex flex-col gap-1.5">
<h3 className="text-lg font-semibold">Add new account with</h3>
<div className="flex h-11 items-center overflow-hidden rounded-lg bg-neutral-100 p-1 dark:bg-neutral-900">
<button
type="button"
className="h-full flex-1 rounded-md bg-white text-sm font-medium dark:bg-black"
>
nsec
</button>
<button
type="button"
className="flex h-full flex-1 flex-col items-center justify-center rounded-md text-sm font-medium"
>
<span className="leading-tight">nsecBunker</span>
<span className="text-xs font-normal leading-tight text-neutral-700 dark:text-neutral-300">
coming soon
</span>
</button>
<button
type="button"
className="flex h-full flex-1 flex-col items-center justify-center rounded-md text-sm font-medium"
>
<span className="leading-tight">Address</span>
<span className="text-xs font-normal leading-tight text-neutral-700 dark:text-neutral-300">
coming soon
</span>
</button>
</div>
</div>
<div className="flex flex-col gap-5">
<div className="flex flex-col gap-2">
<label htmlFor="nsec">
Enter sign in key start with nsec or ncrypto
</label>
<input
name="nsec"
type="text"
placeholder="nsec or ncrypto..."
value={nsec}
onChange={(e) => setNsec(e.target.value)}
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="nsec">Passphase (optional)</label>
<input
name="passphase"
type="password"
value={passphase}
onChange={(e) => setPassphase(e.target.value)}
className="h-11 w-full resize-none rounded-lg border-transparent bg-neutral-100 placeholder:text-neutral-600 focus:border-blue-500 focus:ring focus:ring-blue-100 dark:bg-neutral-900 dark:focus:ring-blue-900"
/>
</div>
</div>
<div className="flex flex-col gap-3">
<button
type="button"
onClick={login}
className="inline-flex h-11 w-full items-center justify-between gap-1.5 rounded-lg bg-blue-500 px-5 font-medium text-white hover:bg-blue-600"
>
<div className="size-5" />
<div>Add account</div>
<ArrowRightIcon className="size-5" />
</button>
</div>
</div>
</Dialog.Content>
</Dialog.Portal>
</Dialog.Root>
);
}

View File

@ -3,8 +3,8 @@ import { Event } from "@lume/types";
import { cn } from "@lume/utils";
import { useQuery } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { useArk } from "@lume/ark";
import { Note, User } from "@lume/ui";
import { useRouteContext } from "@tanstack/react-router";
export function RepostNote({
event,
@ -13,8 +13,7 @@ export function RepostNote({
event: Event;
className?: string;
}) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
const {
isLoading,

View File

@ -2,16 +2,14 @@ import { ComposeFilledIcon, PlusIcon } from "@lume/icons";
import { Outlet, createFileRoute, useNavigate } from "@tanstack/react-router";
import { cn } from "@lume/utils";
import { Accounts } from "@/components/accounts";
import { useArk } from "@lume/ark";
export const Route = createFileRoute("/$account")({
component: App,
});
function App() {
const ark = useArk();
const navigate = useNavigate();
const { platform } = Route.useRouteContext();
const { ark, platform } = Route.useRouteContext();
return (
<div className="flex h-screen w-screen flex-col">

View File

@ -1,5 +1,4 @@
import { AvatarUploader } from "@/components/avatarUploader";
import { useArk } from "@lume/ark";
import { LoaderIcon, PlusIcon } from "@lume/icons";
import { Metadata } from "@lume/types";
import { createFileRoute, useNavigate } from "@tanstack/react-router";
@ -16,11 +15,11 @@ export const Route = createFileRoute("/auth/new/profile")({
});
function Screen() {
const ark = useArk();
const keys = Route.useLoaderData();
const navigate = useNavigate();
const { t } = useTranslation();
const { ark } = Route.useRouteContext();
const { register, handleSubmit } = useForm();
const [picture, setPicture] = useState<string>("");

View File

@ -1,8 +1,6 @@
import { useArk } from "@lume/ark";
import { LoaderIcon } from "@lume/icons";
import { createLazyFileRoute, useNavigate } from "@tanstack/react-router";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
export const Route = createLazyFileRoute("/auth/privkey")({
@ -10,10 +8,9 @@ export const Route = createLazyFileRoute("/auth/privkey")({
});
function Screen() {
const ark = useArk();
const { ark } = Route.useRouteContext();
const navigate = useNavigate();
const [t] = useTranslation();
const [key, setKey] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);

View File

@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import * as Switch from "@radix-ui/react-switch";
import { useEffect, useState } from "react";
import { Settings } from "@lume/types";
import { useArk } from "@lume/ark";
import {
isPermissionGranted,
requestPermission,
@ -16,12 +15,12 @@ export const Route = createLazyFileRoute("/auth/settings")({
});
function Screen() {
const ark = useArk();
const navigate = useNavigate();
// @ts-ignore, magic!!!
const { account } = Route.useSearch();
const { t } = useTranslation();
const { ark } = Route.useRouteContext();
const [settings, setSettings] = useState<Settings>({
notification: false,

View File

@ -1,4 +1,3 @@
import { useArk } from "@lume/ark";
import { AddMediaIcon, LoaderIcon } from "@lume/icons";
import { cn, insertImage, isImagePath } from "@lume/utils";
import { useEffect, useState } from "react";
@ -6,9 +5,10 @@ import { useSlateStatic } from "slate-react";
import { toast } from "sonner";
import { getCurrent } from "@tauri-apps/api/window";
import { UnlistenFn } from "@tauri-apps/api/event";
import { useRouteContext } from "@tanstack/react-router";
export function MediaButton({ className }: { className?: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const editor = useSlateStatic();
const [loading, setLoading] = useState(false);

View File

@ -1,4 +1,3 @@
import { useArk } from "@lume/ark";
import { LoaderIcon, TrashIcon } from "@lume/icons";
import {
Portal,
@ -61,6 +60,7 @@ export const Route = createFileRoute("/editor/")({
function Screen() {
// @ts-ignore, useless
const { reply_to, quote } = Route.useSearch();
const { ark } = Route.useRouteContext();
let initialValue: EditorElement[];
@ -89,7 +89,6 @@ function Screen() {
];
}
const ark = useArk();
const ref = useRef<HTMLDivElement | null>();
const contacts = useSuspenseQuery(contactQueryOptions).data as Contact[];

View File

@ -1,10 +1,10 @@
import { useArk } from "@lume/ark";
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { EventWithReplies } from "@lume/types";
import { Reply } from "./reply";
import { useRouteContext } from "@tanstack/react-router";
export function ReplyList({
eventId,
@ -13,8 +13,7 @@ export function ReplyList({
eventId: string;
className?: string;
}) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const [t] = useTranslation();
const [data, setData] = useState<null | EventWithReplies[]>(null);

View File

@ -1,4 +1,3 @@
import { useArk } from "@lume/ark";
import { LoaderIcon, PlusIcon } from "@lume/icons";
import { User } from "@lume/ui";
import { Link } from "@tanstack/react-router";
@ -45,7 +44,6 @@ export const Route = createFileRoute("/")({
});
function Screen() {
const ark = useArk();
const navigate = useNavigate();
const context = Route.useRouteContext();
@ -53,8 +51,8 @@ function Screen() {
const select = async (npub: string) => {
setLoading(true);
const loadAccount = await ark.load_selected_account(npub);
context.settings = await ark.get_settings(npub);
const loadAccount = await context.ark.load_selected_account(npub);
context.settings = await context.ark.get_settings(npub);
if (loadAccount) {
navigate({
@ -85,7 +83,7 @@ function Screen() {
</div>
) : (
<>
{ark.accounts.map((account) => (
{context.ark.accounts.map((account) => (
<button
type="button"
key={account.npub}

View File

@ -1,10 +1,10 @@
import { RepostNote } from "@/components/repost";
import { Suggest } from "@/components/suggest";
import { TextNote } from "@/components/text";
import { useEvents } from "@lume/ark";
import { LoaderIcon, ArrowRightCircleIcon, InfoIcon } from "@lume/icons";
import { Event, Kind } from "@lume/types";
import { Column } from "@lume/ui";
import { useInfiniteQuery } from "@tanstack/react-query";
import { createLazyFileRoute } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
import { Virtualizer } from "virtua";
@ -16,9 +16,24 @@ export const Route = createLazyFileRoute("/newsfeed")({
export function Screen() {
// @ts-ignore, just work!!!
const { id, name, account } = Route.useSearch();
const { ark } = Route.useRouteContext();
const { t } = useTranslation();
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
useEvents("local", account);
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } =
useInfiniteQuery({
queryKey: ["local", account],
initialPageParam: 0,
queryFn: async ({ pageParam }: { pageParam: number }) => {
const events = await ark.get_events("local", 20, pageParam, true);
return events;
},
getNextPageParam: (lastPage) => {
const lastEvent = lastPage?.at(-1);
if (!lastEvent) return;
return lastEvent.created_at - 1;
},
select: (data) => data?.pages.flatMap((page) => page),
refetchOnWindowFocus: false,
});
const renderItem = (event: Event) => {
if (!event) return;

View File

@ -1,4 +1,3 @@
import { useArk } from "@lume/ark";
import { ArrowRightIcon, ZapIcon } from "@lume/icons";
import { Container } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router";
@ -9,7 +8,7 @@ export const Route = createLazyFileRoute("/nwc")({
});
function Screen() {
const ark = useArk();
const { ark } = Route.useRouteContext();
const [uri, setUri] = useState("");
const [isDone, setIsDone] = useState(false);

View File

@ -1,13 +1,13 @@
import { TextNote } from "@/components/text";
import { RepostNote } from "@/components/repost";
import { useArk } from "@lume/ark";
import { ArrowRightCircleIcon, InfoIcon, LoaderIcon } from "@lume/icons";
import { Event, Kind } from "@lume/types";
import { FETCH_LIMIT } from "@lume/utils";
import { useInfiniteQuery } from "@tanstack/react-query";
import { useRouteContext } from "@tanstack/react-router";
export function EventList({ id }: { id: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const { data, hasNextPage, isLoading, isFetchingNextPage, fetchNextPage } =
useInfiniteQuery({
queryKey: ["events", id],

View File

@ -1,5 +1,4 @@
import { Balance } from "@/components/balance";
import { useArk } from "@lume/ark";
import { Box, Container, User } from "@lume/ui";
import { createLazyFileRoute } from "@tanstack/react-router";
import { useState } from "react";
@ -16,6 +15,7 @@ export const Route = createLazyFileRoute("/zap/$id")({
function Screen() {
const { t } = useTranslation();
const { ark } = Route.useRouteContext();
const { id } = Route.useParams();
// @ts-ignore, magic !!!
const { pubkey, account } = Route.useSearch();
@ -25,8 +25,6 @@ function Screen() {
const [isCompleted, setIsCompleted] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const ark = useArk();
const submit = async () => {
try {
// start loading

View File

@ -1,4 +0,0 @@
import { createContext } from "react";
import { type Ark } from "./ark";
export const ArkContext = createContext<Ark | null>(undefined);

View File

@ -1,10 +0,0 @@
import { useContext } from "react";
import { ArkContext } from "../context";
export const useArk = () => {
const context = useContext(ArkContext);
if (context === undefined) {
throw new Error("useArk must be used within an ArkProvider");
}
return context;
};

View File

@ -1,13 +1,18 @@
import { Event } from "@lume/types";
import { useQuery } from "@tanstack/react-query";
import { useArk } from "./useArk";
import { invoke } from "@tauri-apps/api/core";
export function useEvent(id: string) {
const ark = useArk();
const { isLoading, isError, data } = useQuery({
queryKey: ["event", id],
queryFn: async () => {
try {
const event = await ark.get_event(id);
const eventId: string = id
.replace("nostr:", "")
.split("'")[0]
.split(".")[0];
const cmd: string = await invoke("get_event", { id: eventId });
const event: Event = JSON.parse(cmd);
return event;
} catch (e) {
throw new Error(e);

View File

@ -1,48 +0,0 @@
import { useInfiniteQuery } from "@tanstack/react-query";
import { useArk } from "./useArk";
const FETCH_LIMIT = 20;
const QUERY_KEY = "local";
const DEDUP = true;
export function useEvents(key: string, account?: string) {
const ark = useArk();
const {
data,
isError,
isLoading,
isRefetching,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
} = useInfiniteQuery({
queryKey: [key, account],
initialPageParam: 0,
queryFn: async ({ pageParam }: { pageParam: number }) => {
const events = await ark.get_events(
QUERY_KEY,
FETCH_LIMIT,
pageParam,
DEDUP,
);
return events;
},
getNextPageParam: (lastPage) => {
const lastEvent = lastPage?.at(-1);
if (!lastEvent) return;
return lastEvent.created_at - 1;
},
select: (data) => data?.pages.flatMap((page) => page),
refetchOnWindowFocus: false,
});
return {
data,
isError,
isLoading,
isRefetching,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
};
}

View File

@ -1,8 +1,8 @@
import { useQuery } from "@tanstack/react-query";
import { useArk } from "./useArk";
import { Metadata } from "@lume/types";
import { invoke } from "@tauri-apps/api/core";
export function useProfile(pubkey: string) {
const ark = useArk();
const {
isLoading,
isError,
@ -11,8 +11,14 @@ export function useProfile(pubkey: string) {
queryKey: ["user", pubkey],
queryFn: async () => {
try {
const profile = await ark.get_profile(pubkey);
return profile;
const id = pubkey
.replace("nostr:", "")
.split("'")[0]
.split(".")[0]
.split(",")[0]
.split("?")[0];
const cmd: Metadata = await invoke("get_profile", { id });
return cmd;
} catch (e) {
throw new Error(e);
}

View File

@ -1,6 +1,4 @@
export * from "./ark";
export * from "./context";
export * from "./hooks/useArk";
export * from "./hooks/useEvent";
export * from "./hooks/useEvents";
export * from "./hooks/useProfile";

View File

@ -1,53 +0,0 @@
import { useArk } from "@lume/ark";
import { useQuery } from "@tanstack/react-query";
export function AppHandler({ tag }: { tag: string[] }) {
const ark = useArk();
const { isLoading, isError, data } = useQuery({
queryKey: ["app-handler", tag[1]],
queryFn: async () => {
const ref = tag[1].split(":");
const event = await ark.getEventByFilter({
filter: {
kinds: [Number(ref[0])],
authors: [ref[1]],
"#d": [ref[2]],
},
});
if (!event) return null;
const app = NDKAppHandlerEvent.from(event);
return await app.fetchProfile();
},
refetchOnWindowFocus: false,
});
if (isLoading) {
<div>Loading...</div>;
}
if (isError || !data) {
return <div>Error</div>;
}
return (
<div className="flex items-center gap-2 rounded-md bg-neutral-200 p-2 hover:ring-1 hover:ring-blue-500 dark:bg-neutral-800">
<img
src={data?.picture || data?.image}
alt={data.pubkey}
decoding="async"
className="h-9 w-9 shrink-0 rounded-lg bg-white object-cover ring-1 ring-neutral-200/50 dark:ring-neutral-800/50"
/>
<div className="flex flex-col">
<div className="max-w-[15rem] truncate font-semibold text-neutral-950 dark:text-neutral-50">
{data.name}
</div>
<div className="line-clamp-1 text-sm text-neutral-600 dark:text-neutral-400">
{data.about}
</div>
</div>
</div>
);
}

View File

@ -1,13 +1,13 @@
import { ArrowDownIcon, ArrowUpIcon, LoaderIcon } from "@lume/icons";
import { ArrowDownIcon, LoaderIcon } from "@lume/icons";
import { useState } from "react";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
import { cn } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next";
import { useRouteContext } from "@tanstack/react-router";
export function NoteDownvote() {
const ark = useArk();
const ark = useRouteContext({ strict: false });
const event = useNoteContext();
const [t] = useTranslation();
@ -48,7 +48,7 @@ export function NoteDownvote() {
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] dark:bg-neutral-50 dark:text-neutral-950">
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
{t("note.buttons.downvote")}
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>

View File

@ -2,12 +2,12 @@ import { ReplyIcon } from "@lume/icons";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
import { useRouteContext } from "@tanstack/react-router";
export function NoteReply() {
const ark = useArk();
const event = useNoteContext();
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
return (
@ -23,7 +23,7 @@ export function NoteReply() {
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] dark:bg-neutral-50 dark:text-neutral-950">
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
{t("note.menu.viewThread")}
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>

View File

@ -1,4 +1,4 @@
import { LoaderIcon, QuoteIcon, ReplyIcon, RepostIcon } from "@lume/icons";
import { LoaderIcon, QuoteIcon, RepostIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import * as Tooltip from "@radix-ui/react-tooltip";
@ -6,10 +6,10 @@ import { useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
import { useRouteContext } from "@tanstack/react-router";
export function NoteRepost() {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const event = useNoteContext();
const [t] = useTranslation();
@ -59,7 +59,7 @@ export function NoteRepost() {
</Tooltip.Trigger>
</DropdownMenu.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] dark:bg-neutral-50 dark:text-neutral-950">
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
{t("note.buttons.repost")}
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>

View File

@ -1,13 +1,13 @@
import { ArrowUpIcon, LoaderIcon } from "@lume/icons";
import { useState } from "react";
import { useNoteContext } from "../provider";
import { useArk } from "@lume/ark";
import { cn } from "@lume/utils";
import * as Tooltip from "@radix-ui/react-tooltip";
import { useTranslation } from "react-i18next";
import { useRouteContext } from "@tanstack/react-router";
export function NoteUpvote() {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const event = useNoteContext();
const [t] = useTranslation();
@ -48,7 +48,7 @@ export function NoteUpvote() {
</button>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content className="data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] dark:bg-neutral-50 dark:text-neutral-950">
<Tooltip.Content className="inline-flex h-7 select-none items-center justify-center rounded-md bg-neutral-950 px-3.5 text-sm text-neutral-50 will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade dark:bg-neutral-50 dark:text-neutral-950">
{t("note.buttons.upvote")}
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
</Tooltip.Content>

View File

@ -1,13 +1,11 @@
import { useArk } from "@lume/ark";
import { ZapIcon } from "@lume/icons";
import { toast } from "sonner";
import { useNoteContext } from "../provider";
import { useSearch } from "@tanstack/react-router";
import { useRouteContext, useSearch } from "@tanstack/react-router";
export function NoteZap() {
const ark = useArk();
const event = useNoteContext();
const { ark } = useRouteContext({ strict: false });
const { account } = useSearch({ strict: false });
const zap = async () => {

View File

@ -7,7 +7,6 @@ import {
VIDEOS,
cn,
} from "@lume/utils";
import { NIP89 } from "./nip89";
import { useNoteContext } from "./provider";
import { ReactNode, useMemo } from "react";
import { nanoid } from "nanoid";
@ -136,10 +135,6 @@ export function NoteContent({
}
}, []);
if (event.kind !== Kind.Text) {
return <NIP89 className={className} />;
}
return (
<div className={cn("select-text", className)}>
<div className="content-break whitespace-pre-line text-balance leading-normal">

View File

@ -1,7 +1,8 @@
import { useTranslation } from "react-i18next";
import { User } from "../../user";
import { useArk, useEvent } from "@lume/ark";
import { useEvent } from "@lume/ark";
import { LinkIcon } from "@lume/icons";
import { useRouteContext } from "@tanstack/react-router";
export function MentionNote({
eventId,
@ -10,8 +11,7 @@ export function MentionNote({
eventId: string;
openable?: boolean;
}) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
const { isLoading, isError, data } = useEvent(eventId);

View File

@ -1,8 +1,9 @@
import { useArk, useProfile } from "@lume/ark";
import { useProfile } from "@lume/ark";
import { displayNpub } from "@lume/utils";
import { useRouteContext } from "@tanstack/react-router";
export function MentionUser({ pubkey }: { pubkey: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const { isLoading, isError, profile } = useProfile(pubkey);
return (

View File

@ -3,13 +3,13 @@ import * as DropdownMenu from "@radix-ui/react-dropdown-menu";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import { useTranslation } from "react-i18next";
import { useNoteContext } from "./provider";
import { useArk } from "@lume/ark";
import { toast } from "sonner";
import { useRouteContext } from "@tanstack/react-router";
export function NoteMenu() {
const ark = useArk();
const event = useNoteContext();
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
const copyID = async () => {

View File

@ -1,55 +0,0 @@
import { useQuery } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { AppHandler } from "./appHandler";
import { useNoteContext } from "./provider";
import { useArk } from "@lume/ark";
export function NIP89({ className }: { className?: string }) {
const ark = useArk();
const event = useNoteContext();
const { t } = useTranslation();
const { isLoading, isError, data } = useQuery({
queryKey: ["app-recommend", event.id],
queryFn: () => {
return ark.getAppRecommend({
unknownKind: event.kind.toString(),
author: event.pubkey,
});
},
refetchOnWindowFocus: false,
refetchOnMount: false,
staleTime: Infinity,
});
if (isLoading) {
<div>Loading...</div>;
}
if (isError || !data) {
return <div>Error</div>;
}
return (
<div className={className}>
<div className="flex flex-col rounded-lg bg-neutral-100 dark:bg-neutral-900">
<div className="inline-flex h-10 shrink-0 items-center justify-between border-b border-neutral-200 px-3 dark:border-neutral-800">
<p className="text-sm font-medium text-amber-400">
{t("nip89.unsupported")}
</p>
<p className="text-sm text-neutral-600 dark:text-neutral-400">
{event.kind}
</p>
</div>
<div className="flex flex-1 flex-col gap-2 px-3 py-3">
<span className="text-sm font-medium uppercase text-neutral-600 dark:text-neutral-400">
{t("nip89.openWith")}
</span>
{data.map((item) => (
<AppHandler key={item[1]} tag={item} />
))}
</div>
</div>
</div>
);
}

View File

@ -5,7 +5,7 @@ import { useQuery } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";
import { Note } from "..";
import { User } from "../../user";
import { useArk } from "@lume/ark";
import { useRouteContext } from "@tanstack/react-router";
export function RepostNote({
event,
@ -14,8 +14,7 @@ export function RepostNote({
event: Event;
className?: string;
}) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const { t } = useTranslation();
const {
isLoading,

View File

@ -2,11 +2,11 @@ import { cn } from "@lume/utils";
import { useTranslation } from "react-i18next";
import { Note } from ".";
import { useNoteContext } from "./provider";
import { useArk } from "@lume/ark";
import { LinkIcon } from "@lume/icons";
import { useRouteContext } from "@tanstack/react-router";
export function NoteThread({ className }: { className?: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const event = useNoteContext();
const thread = ark.parse_event_thread({
content: event.content,

View File

@ -2,10 +2,10 @@ import { cn } from "@lume/utils";
import * as HoverCard from "@radix-ui/react-hover-card";
import { User } from "../user";
import { useNoteContext } from "./provider";
import { useArk } from "@lume/ark";
import { useRouteContext } from "@tanstack/react-router";
export function NoteUser({ className }: { className?: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const event = useNoteContext();
return (
@ -32,7 +32,7 @@ export function NoteUser({ className }: { className?: string }) {
</User.Root>
<HoverCard.Portal>
<HoverCard.Content
className="data-[side=bottom]:animate-slideUpAndFade w-[300px] rounded-xl bg-black p-3 data-[state=open]:transition-all dark:bg-white dark:shadow-none"
className="w-[300px] rounded-xl bg-black p-3 data-[side=bottom]:animate-slideUpAndFade data-[state=open]:transition-all dark:bg-white dark:shadow-none"
sideOffset={5}
>
<div className="flex flex-col gap-2">

View File

@ -1,12 +1,12 @@
import { useArk } from "@lume/ark";
import { LoaderIcon } from "@lume/icons";
import { cn } from "@lume/utils";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useUserContext } from "./provider";
import { useRouteContext } from "@tanstack/react-router";
export function UserFollowButton({ className }: { className?: string }) {
const ark = useArk();
const { ark } = useRouteContext({ strict: false });
const user = useUserContext();
const [t] = useTranslation();

View File

@ -2,12 +2,12 @@ import { VerifiedIcon } from "@lume/icons";
import { cn, displayLongHandle, displayNpub } from "@lume/utils";
import { useQuery } from "@tanstack/react-query";
import { useUserContext } from "./provider";
import { useArk } from "@lume/ark";
import { useRouteContext } from "@tanstack/react-router";
export function UserNip05({ className }: { className?: string }) {
const ark = useArk();
const user = useUserContext();
const { ark } = useRouteContext({ strict: false });
const { isLoading, data: verified } = useQuery({
queryKey: ["nip05", user?.pubkey],
queryFn: async () => {