wip: refactor
This commit is contained in:
16
src/app.tsx
16
src/app.tsx
@@ -5,18 +5,18 @@ import { AuthImportScreen } from '@app/auth/import';
|
||||
import { OnboardingScreen } from '@app/auth/onboarding';
|
||||
import { ErrorScreen } from '@app/error';
|
||||
|
||||
import { getActiveAccount } from '@libs/storage';
|
||||
|
||||
import { AppLayout } from '@shared/appLayout';
|
||||
import { AuthLayout } from '@shared/authLayout';
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
import { SettingsLayout } from '@shared/settingsLayout';
|
||||
|
||||
import { checkActiveAccount } from '@utils/checkActiveAccount';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const appLoader = async () => {
|
||||
async function Loader() {
|
||||
try {
|
||||
const account = await getActiveAccount();
|
||||
const account = await checkActiveAccount();
|
||||
const stronghold = sessionStorage.getItem('stronghold');
|
||||
const privkey = JSON.parse(stronghold).state.privkey || null;
|
||||
const onboarding = localStorage.getItem('onboarding');
|
||||
@@ -29,10 +29,6 @@ const appLoader = async () => {
|
||||
if (!account) {
|
||||
return redirect('/auth/welcome');
|
||||
} else {
|
||||
if (account.privkey.length > 35) {
|
||||
return redirect('/auth/migrate');
|
||||
}
|
||||
|
||||
if (!privkey) {
|
||||
return redirect('/auth/unlock');
|
||||
}
|
||||
@@ -42,14 +38,14 @@ const appLoader = async () => {
|
||||
} catch (e) {
|
||||
throw new Error('App failed to load');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <AppLayout />,
|
||||
errorElement: <ErrorScreen />,
|
||||
loader: appLoader,
|
||||
loader: Loader,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
|
||||
@@ -10,7 +10,6 @@ import { ArrowRightCircleIcon, LoaderIcon } from '@shared/icons';
|
||||
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
|
||||
export function ImportStep3Screen() {
|
||||
@@ -21,7 +20,6 @@ export function ImportStep3Screen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const { db } = useStorage();
|
||||
const { status, account } = useAccount();
|
||||
const { fetchUserData } = useNostr();
|
||||
|
||||
const submit = async () => {
|
||||
@@ -72,7 +70,7 @@ export function ImportStep3Screen() {
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<User pubkey={account.pubkey} />
|
||||
<User pubkey={db.account.pubkey} />
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-11 w-full items-center justify-between gap-2 rounded-lg bg-fuchsia-500 px-6 font-medium leading-none text-white hover:bg-fuchsia-600 focus:outline-none"
|
||||
|
||||
@@ -40,7 +40,6 @@ export function MigrateScreen() {
|
||||
const [passwordInput, setPasswordInput] = useState('password');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const { account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
|
||||
// toggle private key
|
||||
@@ -69,10 +68,10 @@ export function MigrateScreen() {
|
||||
const stronghold = await Stronghold.load(`${dir}/lume.stronghold`, data.password);
|
||||
|
||||
if (!db.secureDB) db.secureDB = stronghold;
|
||||
await db.secureSave(account.pubkey, account.privkey);
|
||||
await db.secureSave(db.account.pubkey, db.account.privkey);
|
||||
|
||||
// add privkey to state
|
||||
setPrivkey(account.privkey);
|
||||
setPrivkey(db.account.privkey);
|
||||
// remove privkey in db
|
||||
await removePrivkey();
|
||||
// clear cache
|
||||
|
||||
@@ -10,7 +10,6 @@ import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons
|
||||
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import { arrayToNIP02 } from '@utils/transform';
|
||||
|
||||
@@ -21,7 +20,6 @@ export function OnboardStep1Screen() {
|
||||
|
||||
const { db } = useStorage();
|
||||
const { publish, fetchUserData } = useNostr();
|
||||
const { account } = useAccount();
|
||||
const { status, data } = useQuery(['trending-profiles'], async () => {
|
||||
const res = await fetch('https://api.nostr.band/v0/trending/profiles');
|
||||
if (!res.ok) {
|
||||
@@ -45,7 +43,7 @@ export function OnboardStep1Screen() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const tags = arrayToNIP02([...follows, account.pubkey]);
|
||||
const tags = arrayToNIP02([...follows, db.account.pubkey]);
|
||||
const event = await publish({ content: '', kind: 3, tags: tags });
|
||||
await db.updateAccount('follows', follows);
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ArrowRightCircleIcon, CheckCircleIcon, LoaderIcon } from '@shared/icons
|
||||
import { FULL_RELAYS } from '@stores/constants';
|
||||
import { useOnboarding } from '@stores/onboarding';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
|
||||
export function OnboardStep3Screen() {
|
||||
@@ -22,15 +21,17 @@ export function OnboardStep3Screen() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [relays, setRelays] = useState(new Set<string>());
|
||||
|
||||
const { db } = useStorage();
|
||||
const { publish } = useNostr();
|
||||
const { account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
const { ndk } = useNDK();
|
||||
const { status, data } = useQuery(
|
||||
['relays'],
|
||||
async () => {
|
||||
const tmp = new Map<string, string>();
|
||||
const events = await ndk.fetchEvents({ kinds: [10002], authors: account.follows });
|
||||
const events = await ndk.fetchEvents({
|
||||
kinds: [10002],
|
||||
authors: db.account.follows,
|
||||
});
|
||||
|
||||
if (events) {
|
||||
events.forEach((event) => {
|
||||
@@ -43,7 +44,8 @@ export function OnboardStep3Screen() {
|
||||
return tmp;
|
||||
},
|
||||
{
|
||||
enabled: account ? true : false,
|
||||
enabled: db.account ? true : false,
|
||||
refetchOnWindowFocus: false,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ export function ResetScreen() {
|
||||
const [passwordInput, setPasswordInput] = useState('password');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const { account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
|
||||
// toggle private key
|
||||
@@ -69,7 +68,7 @@ export function ResetScreen() {
|
||||
|
||||
const tmpPubkey = getPublicKey(privkey);
|
||||
|
||||
if (tmpPubkey !== account.pubkey) {
|
||||
if (tmpPubkey !== db.account.pubkey) {
|
||||
setLoading(false);
|
||||
setError('password', {
|
||||
type: 'custom',
|
||||
@@ -88,10 +87,10 @@ export function ResetScreen() {
|
||||
);
|
||||
|
||||
if (!db.secureDB) db.secureDB = stronghold;
|
||||
await db.secureSave(account.pubkey, account.privkey);
|
||||
await db.secureSave(db.account.pubkey, db.account.privkey);
|
||||
|
||||
// add privkey to state
|
||||
setPrivkey(account.privkey);
|
||||
setPrivkey(db.account.privkey);
|
||||
// redirect to home
|
||||
navigate('/auth/unlock', { replace: true });
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import { EyeOffIcon, EyeOnIcon, LoaderIcon } from '@shared/icons';
|
||||
|
||||
import { useStronghold } from '@stores/stronghold';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
type FormValues = {
|
||||
password: string;
|
||||
};
|
||||
@@ -37,7 +35,6 @@ export function UnlockScreen() {
|
||||
const [showPassword, setShowPassword] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const { account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
|
||||
const {
|
||||
@@ -56,7 +53,7 @@ export function UnlockScreen() {
|
||||
|
||||
if (!db.secureDB) db.secureDB = stronghold;
|
||||
|
||||
const privkey = await db.secureLoad(account.pubkey);
|
||||
const privkey = await db.secureLoad(db.account.pubkey);
|
||||
|
||||
setPrivkey(privkey);
|
||||
// redirect to home
|
||||
|
||||
@@ -6,24 +6,22 @@ import { NewMessageModal } from '@app/chats/components/modal';
|
||||
import { ChatsListSelfItem } from '@app/chats/components/self';
|
||||
import { UnknownsModal } from '@app/chats/components/unknowns';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { getChats } from '@libs/storage';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { Chats } from '@utils/types';
|
||||
|
||||
export function ChatsList() {
|
||||
const { account } = useAccount();
|
||||
const {
|
||||
status,
|
||||
data: chats,
|
||||
isFetching,
|
||||
} = useQuery(['chats'], async () => {
|
||||
const { db } = useStorage();
|
||||
const { ndk } = useNDK();
|
||||
const { status, data: chats } = useQuery(['chats'], async () => {
|
||||
return await getChats();
|
||||
});
|
||||
|
||||
const renderItem = useCallback(
|
||||
(item: Chats) => {
|
||||
if (account?.pubkey !== item.sender_pubkey) {
|
||||
if (db.account.pubkey !== item.sender_pubkey) {
|
||||
return <ChatsListItem key={item.sender_pubkey} data={item} />;
|
||||
}
|
||||
},
|
||||
@@ -47,21 +45,8 @@ export function ChatsList() {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
{account ? (
|
||||
<ChatsListSelfItem data={account} />
|
||||
) : (
|
||||
<div className="inline-flex h-9 items-center gap-2.5 rounded-md px-2">
|
||||
<div className="relative h-6 w-6 shrink-0 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-3 w-full animate-pulse rounded-sm bg-white/10" />
|
||||
</div>
|
||||
)}
|
||||
<ChatsListSelfItem pubkey={db.account.pubkey} />
|
||||
{chats.follows.map((item) => renderItem(item))}
|
||||
{isFetching && (
|
||||
<div className="inline-flex h-9 items-center gap-2.5 rounded-md px-2">
|
||||
<div className="relative h-6 w-6 shrink-0 animate-pulse rounded bg-white/10" />
|
||||
<div className="h-3 w-full animate-pulse rounded-sm bg-white/10" />
|
||||
</div>
|
||||
)}
|
||||
{chats.unknowns.length > 0 && <UnknownsModal data={chats.unknowns} />}
|
||||
<NewMessageModal />
|
||||
</div>
|
||||
|
||||
@@ -4,15 +4,15 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { User } from '@app/auth/components/user';
|
||||
|
||||
import { CancelIcon, LoaderIcon, PlusIcon } from '@shared/icons';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { CancelIcon, LoaderIcon, PlusIcon } from '@shared/icons';
|
||||
|
||||
export function NewMessageModal() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const { status, account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
|
||||
const openChat = (pubkey: string) => {
|
||||
setOpen(false);
|
||||
@@ -59,7 +59,7 @@ export function NewMessageModal() {
|
||||
<LoaderIcon className="h-5 w-5 animate-spin text-white" />
|
||||
</div>
|
||||
) : (
|
||||
account?.follows?.map((follow) => (
|
||||
db.account?.follows?.map((follow) => (
|
||||
<div
|
||||
key={follow}
|
||||
className="group flex items-center justify-between px-4 py-2 hover:bg-white/10"
|
||||
|
||||
@@ -8,8 +8,8 @@ import { DEFAULT_AVATAR } from '@stores/constants';
|
||||
import { useProfile } from '@utils/hooks/useProfile';
|
||||
import { displayNpub } from '@utils/shortenKey';
|
||||
|
||||
export function ChatsListSelfItem({ data }: { data: { pubkey: string } }) {
|
||||
const { status, user } = useProfile(data.pubkey);
|
||||
export function ChatsListSelfItem({ pubkey }: { pubkey: string }) {
|
||||
const { status, user } = useProfile(pubkey);
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
@@ -22,7 +22,7 @@ export function ChatsListSelfItem({ data }: { data: { pubkey: string } }) {
|
||||
|
||||
return (
|
||||
<NavLink
|
||||
to={`/chats/${data.pubkey}`}
|
||||
to={`/chats/${pubkey}`}
|
||||
preventScrollReset={true}
|
||||
className={({ isActive }) =>
|
||||
twMerge(
|
||||
@@ -34,12 +34,12 @@ export function ChatsListSelfItem({ data }: { data: { pubkey: string } }) {
|
||||
<Image
|
||||
src={user?.picture || user?.image}
|
||||
fallback={DEFAULT_AVATAR}
|
||||
alt={data.pubkey}
|
||||
alt={pubkey}
|
||||
className="h-6 w-6 shrink-0 rounded bg-white object-cover"
|
||||
/>
|
||||
<div className="inline-flex items-baseline gap-1">
|
||||
<h5 className="max-w-[10rem] truncate">
|
||||
{user?.nip05 || user?.name || displayNpub(data.pubkey, 16)}
|
||||
{user?.nip05 || user?.name || displayNpub(pubkey, 16)}
|
||||
</h5>
|
||||
<span className="text-white/50">(you)</span>
|
||||
</div>
|
||||
|
||||
@@ -10,10 +10,10 @@ import { ChatSidebar } from '@app/chats/components/sidebar';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { createChat, getChatMessages } from '@libs/storage';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { useStronghold } from '@stores/stronghold';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { Chats } from '@utils/types';
|
||||
|
||||
export function ChatScreen() {
|
||||
@@ -21,17 +21,11 @@ export function ChatScreen() {
|
||||
const virtuosoRef = useRef(null);
|
||||
|
||||
const { ndk } = useNDK();
|
||||
const { db } = useStorage();
|
||||
const { pubkey } = useParams();
|
||||
const { account } = useAccount();
|
||||
const { status, data } = useQuery(
|
||||
['chat', pubkey],
|
||||
async () => {
|
||||
return await getChatMessages(account.pubkey, pubkey);
|
||||
},
|
||||
{
|
||||
enabled: account ? true : false,
|
||||
}
|
||||
);
|
||||
const { status, data } = useQuery(['chat', pubkey], async () => {
|
||||
return await getChatMessages(db.account.pubkey, pubkey);
|
||||
});
|
||||
|
||||
const userPrivkey = useStronghold((state) => state.privkey);
|
||||
|
||||
@@ -40,7 +34,7 @@ export function ChatScreen() {
|
||||
return (
|
||||
<ChatMessageItem
|
||||
data={data[index]}
|
||||
userPubkey={account.pubkey}
|
||||
userPubkey={db.account.pubkey}
|
||||
userPrivkey={userPrivkey}
|
||||
/>
|
||||
);
|
||||
@@ -75,7 +69,7 @@ export function ChatScreen() {
|
||||
const sub: NDKSubscription = ndk.subscribe(
|
||||
{
|
||||
kinds: [4],
|
||||
authors: [account.pubkey],
|
||||
authors: [db.account.pubkey],
|
||||
'#p': [pubkey],
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
},
|
||||
@@ -129,7 +123,7 @@ export function ChatScreen() {
|
||||
<div className="z-50 shrink-0 rounded-b-xl border-t border-white/5 bg-white/10 p-3 px-5">
|
||||
<ChatMessageForm
|
||||
receiverPubkey={pubkey}
|
||||
userPubkey={account.pubkey}
|
||||
userPubkey={db.account.pubkey}
|
||||
userPrivkey={userPrivkey}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,79 @@
|
||||
import { useRouteError } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useRouteError } from 'react-router-dom';
|
||||
|
||||
interface IRouteError {
|
||||
statusText: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface IDebugInfo {
|
||||
os: null | string;
|
||||
version: null | string;
|
||||
}
|
||||
|
||||
export function ErrorScreen() {
|
||||
const error = useRouteError() as IRouteError;
|
||||
const location = useLocation();
|
||||
|
||||
const [debugInfo, setDebugInfo] = useState<IDebugInfo>({ os: null, version: null });
|
||||
|
||||
useEffect(() => {
|
||||
async function getInformation() {
|
||||
const { platform, version } = await import('@tauri-apps/plugin-os');
|
||||
const { getVersion } = await import('@tauri-apps/plugin-app');
|
||||
|
||||
const platformName = await platform();
|
||||
const osVersion = await version();
|
||||
const appVersion = await getVersion();
|
||||
|
||||
setDebugInfo({ os: platformName + ' ' + osVersion, version: appVersion });
|
||||
}
|
||||
|
||||
getInformation();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<div>
|
||||
<h1>Oops!</h1>
|
||||
<p>Sorry, an unexpected error has occurred.</p>
|
||||
<p>
|
||||
<i>{error.statusText || error.message}</i>
|
||||
</p>
|
||||
<div className="flex h-full w-full items-center justify-center bg-black/90">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col">
|
||||
<h1 className="mb-1 text-2xl font-semibold text-white">
|
||||
Sorry, an unexpected error has occurred.
|
||||
</h1>
|
||||
<div className="mt-4 inline-flex h-16 items-center justify-center rounded-xl border border-dashed border-red-400 bg-red-200/10 px-5">
|
||||
<p className="text-sm font-medium text-red-400">
|
||||
{error.statusText || error.message}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="font-medium text-white/50">
|
||||
Current location: {location.pathname}
|
||||
</p>
|
||||
<p className="font-medium text-white/50">App version: {debugInfo.version}</p>
|
||||
<p className="font-medium text-white/50">Platform: {debugInfo.os}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<a
|
||||
href="https://github.com/luminous-devs/lume/issues/new"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-white/10 text-sm font-medium text-white hover:bg-white/20"
|
||||
>
|
||||
Click here to report the issue on GitHub
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-white/10 text-sm font-medium text-white hover:bg-white/20"
|
||||
>
|
||||
Reload app
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex h-11 w-full items-center justify-center rounded-lg bg-white/10 text-sm font-medium text-white hover:bg-white/20"
|
||||
>
|
||||
Reset app
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import {
|
||||
NoteActions,
|
||||
NoteContent,
|
||||
@@ -10,12 +12,11 @@ import {
|
||||
import { RepliesList } from '@shared/notes/replies/list';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { useEvent } from '@utils/hooks/useEvent';
|
||||
|
||||
export function EventScreen() {
|
||||
const { id } = useParams();
|
||||
const { account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
const { status, data } = useEvent(id);
|
||||
|
||||
return (
|
||||
@@ -42,7 +43,7 @@ export function EventScreen() {
|
||||
</div>
|
||||
)}
|
||||
<div className="px-3">
|
||||
<NoteReplyForm id={id} pubkey={account.pubkey} />
|
||||
<NoteReplyForm id={id} pubkey={db.account.pubkey} />
|
||||
<RepliesList id={id} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { NostrEvent } from 'nostr-fetch';
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useNewsfeed } from '@app/space/hooks/useNewsfeed';
|
||||
|
||||
import { getNotes } from '@libs/storage';
|
||||
|
||||
import { NoteKind_1, NoteKind_1063, NoteThread, Repost } from '@shared/notes';
|
||||
import { NoteKindUnsupport } from '@shared/notes/kinds/unsupport';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
import { TitleBar } from '@shared/titleBar';
|
||||
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
import { LumeEvent } from '@utils/types';
|
||||
|
||||
const ITEM_PER_PAGE = 10;
|
||||
|
||||
export function NetworkBlock() {
|
||||
// subscribe for live update
|
||||
// useNewsfeed();
|
||||
|
||||
const { status, data, fetchNextPage, hasNextPage, isFetchingNextPage } =
|
||||
const { fetchNotes } = useNostr();
|
||||
const { status, data, hasNextPage, isFetchingNextPage, fetchNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: ['network-widget'],
|
||||
queryFn: async ({ pageParam = 0 }) => {
|
||||
return await getNotes(ITEM_PER_PAGE, pageParam);
|
||||
queryFn: async ({ pageParam = 24 }) => {
|
||||
return await fetchNotes(pageParam);
|
||||
},
|
||||
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
||||
});
|
||||
|
||||
const notes = data ? data.pages.flatMap((d: { data: LumeEvent[] }) => d.data) : [];
|
||||
const parentRef = useRef();
|
||||
const notes = useMemo(
|
||||
// @ts-expect-error, todo
|
||||
() => (data ? data.pages.flatMap((d: { data: NostrEvent[] }) => d.data) : []),
|
||||
[data]
|
||||
);
|
||||
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: hasNextPage ? notes.length + 1 : notes.length,
|
||||
@@ -140,7 +138,7 @@ export function NetworkBlock() {
|
||||
<div className="rounded-xl bg-white/10 px-3 py-6">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<p className="text-center text-sm text-white">
|
||||
You not have any posts to see yet
|
||||
You not have any postrs to see yet
|
||||
<br />
|
||||
Follow more people to have more fun.
|
||||
</p>
|
||||
@@ -148,7 +146,7 @@ export function NetworkBlock() {
|
||||
to="/trending"
|
||||
className="inline-flex w-max rounded bg-fuchsia-500 px-2.5 py-1.5 text-sm hover:bg-fuchsia-600"
|
||||
>
|
||||
Trending
|
||||
Trending users
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,11 @@ import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { LoaderIcon } from '@shared/icons';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { useNostr } from '@utils/hooks/useNostr';
|
||||
|
||||
export function SplashScreen() {
|
||||
const { db } = useStorage();
|
||||
const { ndk, relayUrls } = useNDK();
|
||||
const { status, account } = useAccount();
|
||||
const { fetchUserData } = useNostr();
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
@@ -46,15 +44,15 @@ export function SplashScreen() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'success' && !account) {
|
||||
if (!db.account) {
|
||||
invoke('close_splashscreen');
|
||||
}
|
||||
|
||||
if (ndk && account) {
|
||||
if (ndk && db.account) {
|
||||
console.log('prefetching...');
|
||||
prefetch();
|
||||
}
|
||||
}, [ndk, account]);
|
||||
}, [ndk, db.account]);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-screen w-screen items-center justify-center bg-black">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { FollowIcon, LoaderIcon, UnfollowIcon } from '@shared/icons';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
|
||||
import { NoteKind_1 } from '@shared/notes';
|
||||
import { NoteSkeleton } from '@shared/notes/skeleton';
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
|
||||
import { Profile } from '@app/trending/components/profile';
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// inspire by: https://github.com/nostr-dev-kit/ndk-react/
|
||||
import NDK from '@nostr-dev-kit/ndk';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import TauriAdapter from '@libs/ndk/cache';
|
||||
@@ -31,14 +30,21 @@ export const NDKInstance = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort('timeout'), 5000);
|
||||
const res = await fetch(url, {
|
||||
headers: { Accept: 'application/nostr+json' },
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
console.log('relay information: ', data);
|
||||
|
||||
verifiedRelays.push(relay);
|
||||
clearTimeout(timeoutId);
|
||||
} else {
|
||||
console.log('relay not working: ', res);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('fetch error', e);
|
||||
|
||||
@@ -36,12 +36,6 @@ export async function getActiveAccount() {
|
||||
'SELECT * FROM accounts WHERE is_active = 1;'
|
||||
);
|
||||
if (result.length > 0) {
|
||||
result[0]['follows'] = result[0].follows
|
||||
? JSON.parse(result[0].follows as unknown as string)
|
||||
: null;
|
||||
result[0]['network'] = result[0].network
|
||||
? JSON.parse(result[0].network as unknown as string)
|
||||
: null;
|
||||
return result[0];
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -7,10 +7,12 @@ import { Account, Relays, Widget } from '@utils/types';
|
||||
export class LumeStorage {
|
||||
public db: Database;
|
||||
public secureDB: Stronghold;
|
||||
public account: Account | null = null;
|
||||
|
||||
constructor(sqlite: Database, stronghold?: Stronghold) {
|
||||
this.db = sqlite;
|
||||
this.secureDB = stronghold ?? undefined;
|
||||
this.account = null;
|
||||
}
|
||||
|
||||
private async getSecureClient() {
|
||||
@@ -45,19 +47,24 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
public async getActiveAccount() {
|
||||
const account: Account = await this.db.select(
|
||||
'SELECT * FROM accounts WHERE is_active = 1;'
|
||||
)?.[0];
|
||||
if (account) {
|
||||
const results: Array<Account> = await this.db.select(
|
||||
'SELECT * FROM accounts WHERE is_active = "1" ORDER BY id DESC LIMIT 1;'
|
||||
);
|
||||
|
||||
if (results.length > 0) {
|
||||
const account = results[0];
|
||||
|
||||
if (typeof account.follows === 'string')
|
||||
account.follows = JSON.parse(account.follows);
|
||||
|
||||
if (typeof account.network === 'string')
|
||||
account.network = JSON.parse(account.network);
|
||||
|
||||
this.account = account;
|
||||
return account;
|
||||
} else {
|
||||
throw new Error('Account not found');
|
||||
console.log('no active account, please create new account');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,27 +82,30 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
public async updateAccount(column: string, value: string | string[]) {
|
||||
const account = await this.getActiveAccount();
|
||||
return await this.db.execute(`UPDATE accounts SET ${column} = $1 WHERE id = $2;`, [
|
||||
value,
|
||||
account.id,
|
||||
]);
|
||||
const insert = await this.db.execute(
|
||||
`UPDATE accounts SET ${column} = $1 WHERE id = $2;`,
|
||||
[value, this.account.id]
|
||||
);
|
||||
|
||||
if (insert) {
|
||||
const account = await this.getActiveAccount();
|
||||
return account;
|
||||
}
|
||||
}
|
||||
|
||||
public async getWidgets() {
|
||||
const account = await this.getActiveAccount();
|
||||
const result: Array<Widget> = await this.db.select(
|
||||
`SELECT * FROM widgets WHERE account_id = "${account.id}" ORDER BY created_at DESC;`
|
||||
`SELECT * FROM widgets WHERE account_id = "${this.account.id}" ORDER BY created_at DESC;`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async createWidget(kind: number, title: string, content: string | string[]) {
|
||||
const account = await this.getActiveAccount();
|
||||
const insert = await this.db.execute(
|
||||
'INSERT OR IGNORE INTO widgets (account_id, kind, title, content) VALUES ($1, $2, $3, $4);',
|
||||
[account.id, kind, title, content]
|
||||
[this.account.id, kind, title, content]
|
||||
);
|
||||
|
||||
if (insert) {
|
||||
const widget: Widget = await this.db.select(
|
||||
'SELECT * FROM widgets ORDER BY id DESC LIMIT 1;'
|
||||
@@ -129,7 +139,7 @@ export class LumeStorage {
|
||||
[cacheKey]
|
||||
)?.[0];
|
||||
if (!event) {
|
||||
console.error('failed to get event by cache_key: ', cacheKey);
|
||||
// console.error('failed to get event by cache_key: ', cacheKey);
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
@@ -141,16 +151,15 @@ export class LumeStorage {
|
||||
[id]
|
||||
)?.[0];
|
||||
if (!event) {
|
||||
console.error('failed to get event by id: ', id);
|
||||
// console.error('failed to get event by id: ', id);
|
||||
return null;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
public async getExplicitRelayUrls() {
|
||||
const account = await this.getActiveAccount();
|
||||
const result: Relays[] = await this.db.select(
|
||||
`SELECT * FROM relays WHERE account_id = "${account.id}";`
|
||||
`SELECT * FROM relays WHERE account_id = "${this.account.id}";`
|
||||
);
|
||||
|
||||
if (result.length > 0) return result.map((el) => el.relay);
|
||||
@@ -158,10 +167,9 @@ export class LumeStorage {
|
||||
}
|
||||
|
||||
public async createRelay(relay: string, purpose?: string) {
|
||||
const account = await this.getActiveAccount();
|
||||
return await this.db.execute(
|
||||
'INSERT OR IGNORE INTO relays (account_id, relay, purpose) VALUES ($1, $2, $3);',
|
||||
[account.id, relay, purpose || '']
|
||||
[this.account.id, relay, purpose || '']
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ const StorageProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
async function initLumeStorage() {
|
||||
const sqlite = await Database.load('sqlite:lume.db');
|
||||
const lumeStorage = new LumeStorage(sqlite);
|
||||
|
||||
if (!lumeStorage.account) await lumeStorage.getActiveAccount();
|
||||
setDB(lumeStorage);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Dialog, Transition } from '@headlessui/react';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { fetch } from '@tauri-apps/plugin-http';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { ActiveAccount } from '@shared/accounts/active';
|
||||
import { SettingsIcon } from '@shared/icons';
|
||||
import { Logout } from '@shared/logout';
|
||||
import { NotificationModal } from '@shared/notification/modal';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function LumeBar() {
|
||||
const { status, account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl bg-white/10 p-2 backdrop-blur-xl">
|
||||
<div className="flex items-center justify-between">
|
||||
{status === 'loading' ? (
|
||||
<>
|
||||
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
|
||||
<div className="group relative flex h-9 w-9 shrink animate-pulse items-center justify-center rounded-md bg-zinc-900" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ActiveAccount data={account} />
|
||||
<NotificationModal pubkey={account.pubkey} />
|
||||
</>
|
||||
)}
|
||||
<ActiveAccount data={db.account} />
|
||||
<NotificationModal pubkey={db.account.pubkey} />
|
||||
<Link
|
||||
to="/settings/general"
|
||||
className="inline-flex h-9 w-9 transform items-center justify-center rounded-md bg-white/20 active:translate-y-1"
|
||||
|
||||
@@ -97,28 +97,6 @@ export function Navigation() {
|
||||
</Collapsible.Content>
|
||||
</div>
|
||||
</Collapsible.Root>
|
||||
<Collapsible.Root open={chats} onOpenChange={setChats}>
|
||||
<div className="flex flex-col gap-1 px-2">
|
||||
<Collapsible.Trigger asChild>
|
||||
<button className="flex items-center gap-1">
|
||||
<div
|
||||
className={twMerge(
|
||||
'inline-flex h-5 w-5 transform items-center justify-center transition-transform duration-150 ease-in-out',
|
||||
open ? '' : 'rotate-180'
|
||||
)}
|
||||
>
|
||||
<NavArrowDownIcon className="h-3 w-3 text-white/50" />
|
||||
</div>
|
||||
<h3 className="text-[11px] font-bold uppercase tracking-widest text-white/50">
|
||||
Chats
|
||||
</h3>
|
||||
</button>
|
||||
</Collapsible.Trigger>
|
||||
<Collapsible.Content>
|
||||
<ChatsList />
|
||||
</Collapsible.Content>
|
||||
</div>
|
||||
</Collapsible.Root>
|
||||
{/* Channels
|
||||
<Disclosure defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
|
||||
@@ -10,8 +10,6 @@ import { NoteZap } from '@shared/notes/actions/zap';
|
||||
import { BLOCK_KINDS } from '@stores/constants';
|
||||
import { useWidgets } from '@stores/widgets';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
|
||||
export function NoteActions({
|
||||
id,
|
||||
pubkey,
|
||||
@@ -23,7 +21,6 @@ export function NoteActions({
|
||||
noOpenThread?: boolean;
|
||||
root?: string;
|
||||
}) {
|
||||
const { account } = useAccount();
|
||||
const setWidget = useWidgets((state) => state.setWidget);
|
||||
|
||||
return (
|
||||
@@ -33,7 +30,7 @@ export function NoteActions({
|
||||
<NoteReply id={id} pubkey={pubkey} root={root} />
|
||||
<NoteReaction id={id} pubkey={pubkey} />
|
||||
<NoteRepost id={id} pubkey={pubkey} />
|
||||
{(account?.lud06 || account?.lud16) && <NoteZap id={id} />}
|
||||
<NoteZap id={id} />
|
||||
</div>
|
||||
{!noOpenThread && (
|
||||
<>
|
||||
|
||||
@@ -67,13 +67,10 @@ export function NoteMetadata({ id }: { id: string }) {
|
||||
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div>
|
||||
<div className="absolute left-[18px] top-14 h-[calc(100%-6.4rem)] w-0.5 bg-gradient-to-t from-white/20 to-white/10" />
|
||||
<div className="relative z-10 flex items-center gap-3 pb-3">
|
||||
<div className="mt-2 h-6 w-11 shrink-0"></div>
|
||||
<div className="mt-2 inline-flex h-6">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
||||
</div>
|
||||
<div className="relative z-10 flex items-center gap-3 pb-3">
|
||||
<div className="mt-2 h-6 w-11 shrink-0"></div>
|
||||
<div className="mt-2 inline-flex h-6">
|
||||
<LoaderIcon className="h-4 w-4 animate-spin text-white" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export const useWidgets = create<WidgetState>()(
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'blocks',
|
||||
name: 'widgets',
|
||||
storage: createJSONStorage(() => localStorage),
|
||||
}
|
||||
)
|
||||
|
||||
33
src/utils/checkActiveAccount.tsx
Normal file
33
src/utils/checkActiveAccount.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import Database from '@tauri-apps/plugin-sql';
|
||||
|
||||
import { Account } from '@utils/types';
|
||||
|
||||
let db: null | Database = null;
|
||||
|
||||
async function connect(): Promise<Database> {
|
||||
if (db) {
|
||||
return db;
|
||||
}
|
||||
try {
|
||||
db = await Database.load('sqlite:lume.db');
|
||||
} catch (e) {
|
||||
throw new Error('Failed to connect to database, error: ', e);
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
export async function checkActiveAccount() {
|
||||
const tempDB = await connect();
|
||||
const result: Array<Account> = await db.select(
|
||||
'SELECT * FROM accounts WHERE is_active = 1;'
|
||||
);
|
||||
|
||||
// close temp db
|
||||
tempDB.close();
|
||||
|
||||
if (result.length > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useNDK } from '@libs/ndk/provider';
|
||||
import { getActiveAccount } from '@libs/storage';
|
||||
import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
export function useAccount() {
|
||||
const { db } = useStorage();
|
||||
const { ndk } = useNDK();
|
||||
const { status, data: account } = useQuery(
|
||||
['account'],
|
||||
async () => {
|
||||
const account = await getActiveAccount();
|
||||
const account = await db.getActiveAccount();
|
||||
console.log('account: ', account);
|
||||
if (account?.pubkey) {
|
||||
const user = ndk.getUser({ hexpubkey: account?.pubkey });
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
NDKUser,
|
||||
} from '@nostr-dev-kit/ndk';
|
||||
import { ndkAdapter } from '@nostr-fetch/adapter-ndk';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import destr from 'destr';
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import { NostrFetcher } from 'nostr-fetch';
|
||||
@@ -19,14 +18,12 @@ import { useStorage } from '@libs/storage/provider';
|
||||
|
||||
import { useStronghold } from '@stores/stronghold';
|
||||
|
||||
import { useAccount } from '@utils/hooks/useAccount';
|
||||
import { nHoursAgo } from '@utils/date';
|
||||
|
||||
export function useNostr() {
|
||||
const { ndk, relayUrls } = useNDK();
|
||||
const { account } = useAccount();
|
||||
const { db } = useStorage();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const privkey = useStronghold((state) => state.privkey);
|
||||
const fetcher = useMemo(() => NostrFetcher.withCustomPool(ndkAdapter(ndk)), [ndk]);
|
||||
const subManager = useMemo(
|
||||
@@ -58,7 +55,7 @@ export function useNostr() {
|
||||
|
||||
// fetch user's follows
|
||||
if (!preFollows) {
|
||||
const user = ndk.getUser({ hexpubkey: account.pubkey });
|
||||
const user = ndk.getUser({ hexpubkey: db.account.pubkey });
|
||||
const list = await user.follows();
|
||||
list.forEach((item: NDKUser) => {
|
||||
follows.add(nip19.decode(item.npub).data as string);
|
||||
@@ -78,8 +75,6 @@ export function useNostr() {
|
||||
await db.updateAccount('follows', [...follows]);
|
||||
await db.updateAccount('network', [...new Set([...follows, ...network])]);
|
||||
|
||||
queryClient.invalidateQueries(['account']);
|
||||
|
||||
return { status: 'ok' };
|
||||
} catch (e) {
|
||||
return { status: 'failed', message: e };
|
||||
@@ -90,19 +85,23 @@ export function useNostr() {
|
||||
try {
|
||||
if (!ndk) return { status: 'failed', message: 'NDK instance not found' };
|
||||
|
||||
const events = await fetcher.fetchAllEvents(
|
||||
relayUrls,
|
||||
{
|
||||
kinds: [1],
|
||||
authors: account.network ?? account.follows,
|
||||
},
|
||||
{ since: since }
|
||||
);
|
||||
const until = since === 24 ? Math.floor(Date.now() / 1000) : nHoursAgo(since / 2);
|
||||
|
||||
return { status: 'ok', notes: events };
|
||||
console.log('fetch events since: ', since);
|
||||
console.log('fetch events until: ', until);
|
||||
/*
|
||||
const events = await ndk.fetchEvents({
|
||||
kinds: [1],
|
||||
authors: db.account.network ?? db.account.follows,
|
||||
since: since,
|
||||
until: until,
|
||||
});
|
||||
*/
|
||||
|
||||
return { status: 'ok', data: [], nextCursor: since * 2 };
|
||||
} catch (e) {
|
||||
console.error('failed get notes, error: ', e);
|
||||
return { status: 'failed', message: e };
|
||||
return { status: 'failed', data: [], message: e };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -123,7 +122,7 @@ export function useNostr() {
|
||||
event.content = content;
|
||||
event.kind = kind;
|
||||
event.created_at = Math.floor(Date.now() / 1000);
|
||||
event.pubkey = account.pubkey;
|
||||
event.pubkey = db.account.pubkey;
|
||||
event.tags = tags;
|
||||
|
||||
await event.sign(signer);
|
||||
|
||||
Reference in New Issue
Block a user