mirror of
https://github.com/luminous-devs/lume.git
synced 2024-09-29 16:30:55 +00:00
Merge pull request #148 from luminous-devs/feat/improve-perf
Improve overall performance
This commit is contained in:
commit
d18de93c60
@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
.shadow-toolbar {
|
||||
box-shadow: 0 0 #0000,0 0 #0000,0 8px 24px 0 rgba(0,0,0,.2),0 2px 8px 0 rgba(0,0,0,.08),inset 0 0 0 1px rgba(0,0,0,.2),inset 0 0 0 2px hsla(0,0%,100%,.14)
|
||||
box-shadow: 0 0 #0000, 0 0 #0000, 0 8px 24px 0 rgba(0, 0, 0, .2), 0 2px 8px 0 rgba(0, 0, 0, .08), inset 0 0 0 1px rgba(0, 0, 0, .2), inset 0 0 0 2px hsla(0, 0%, 100%, .14)
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,3 +42,7 @@ input::-ms-clear {
|
||||
.border {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
media-controller {
|
||||
@apply w-full;
|
||||
}
|
@ -145,7 +145,6 @@ export class Ark {
|
||||
cacheUsage: NDKSubscriptionCacheUsage.CACHE_FIRST,
|
||||
});
|
||||
|
||||
if (!profile) return null;
|
||||
return profile;
|
||||
} catch {
|
||||
throw new Error("user not found");
|
||||
@ -167,8 +166,9 @@ export class Ark {
|
||||
(user) => user.pubkey,
|
||||
);
|
||||
|
||||
if (!pubkey || pubkey === this.account.pubkey)
|
||||
if (!pubkey || pubkey === this.account.pubkey) {
|
||||
this.account.contacts = contacts;
|
||||
}
|
||||
|
||||
return contacts;
|
||||
} catch (e) {
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
MediaPlayButton,
|
||||
MediaTimeDisplay,
|
||||
MediaTimeRange,
|
||||
MediaVolumeRange,
|
||||
} from "media-chrome/dist/react";
|
||||
|
||||
export function VideoPreview({ url }: { url: string }) {
|
||||
@ -24,7 +23,6 @@ export function VideoPreview({ url }: { url: string }) {
|
||||
<MediaTimeRange />
|
||||
<MediaTimeDisplay showDuration />
|
||||
<MediaMuteButton />
|
||||
<MediaVolumeRange />
|
||||
</MediaControlBar>
|
||||
</MediaController>
|
||||
</div>
|
||||
|
@ -39,7 +39,10 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
alt={user.pubkey}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className={cn("bg-black dark:bg-white", className)}
|
||||
className={cn(
|
||||
"bg-black dark:bg-white ring-1 ring-black/5 dark:ring-white/5",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Avatar.Image
|
||||
@ -47,7 +50,7 @@ export function UserAvatar({ className }: { className?: string }) {
|
||||
alt={user.pubkey}
|
||||
loading="eager"
|
||||
decoding="async"
|
||||
className={className}
|
||||
className={cn("ring-1 ring-black/5 dark:ring-white/5", className)}
|
||||
/>
|
||||
)}
|
||||
<Avatar.Fallback delayMs={120}>
|
||||
|
@ -13,6 +13,7 @@ export function UserFollowButton({
|
||||
const [followed, setFollowed] = useState(false);
|
||||
|
||||
const toggleFollow = async () => {
|
||||
setLoading(true);
|
||||
if (!followed) {
|
||||
const add = await ark.createContact(target);
|
||||
if (add) setFollowed(true);
|
||||
@ -20,6 +21,7 @@ export function UserFollowButton({
|
||||
const remove = await ark.deleteContact(target);
|
||||
if (remove) setFollowed(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -37,7 +39,12 @@ export function UserFollowButton({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button type="button" onClick={toggleFollow} className={cn("", className)}>
|
||||
<button
|
||||
type="button"
|
||||
disabled={loading}
|
||||
onClick={toggleFollow}
|
||||
className={cn("", className)}
|
||||
>
|
||||
{loading ? (
|
||||
<LoaderIcon className="size-4 animate-spin" />
|
||||
) : followed ? (
|
||||
|
@ -8,7 +8,7 @@ export function UserName({ className }: { className?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-4 w-20 bg-black/20 dark:bg-white/20 rounded animate-pulse",
|
||||
"h-4 w-20 self-center bg-black/20 dark:bg-white/20 rounded animate-pulse",
|
||||
className,
|
||||
)}
|
||||
/>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useArk } from "./useArk";
|
||||
|
||||
export function useProfile(pubkey: string) {
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
@ -17,6 +19,9 @@ export function useProfile(pubkey: string) {
|
||||
);
|
||||
return profile;
|
||||
},
|
||||
initialData: () => {
|
||||
return queryClient.getQueryData(["user", pubkey]);
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnReconnect: false,
|
||||
|
@ -23,6 +23,7 @@ import { useSetAtom } from "jotai";
|
||||
import Linkify from "linkify-react";
|
||||
import { normalizeRelayUrlSet } from "nostr-fetch";
|
||||
import { PropsWithChildren, useEffect, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Ark } from "./ark";
|
||||
import { LumeContext } from "./context";
|
||||
|
||||
@ -80,55 +81,66 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
|
||||
return new NDKPrivateKeySigner(userPrivkey);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.error(String(e));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function initNDK() {
|
||||
const explicitRelayUrls = normalizeRelayUrlSet([
|
||||
"wss://nostr.mutinywallet.com/",
|
||||
"wss://bostr.nokotaro.com/",
|
||||
]);
|
||||
try {
|
||||
const explicitRelayUrls = normalizeRelayUrlSet([
|
||||
"wss://nostr.mutinywallet.com/",
|
||||
"wss://bostr.nokotaro.com/",
|
||||
"wss://purplepag.es/",
|
||||
]);
|
||||
|
||||
const tauriCache = new NDKCacheAdapterTauri(storage);
|
||||
const ndk = new NDK({
|
||||
cacheAdapter: tauriCache,
|
||||
explicitRelayUrls,
|
||||
enableOutboxModel: !storage.settings.lowPower,
|
||||
autoConnectUserRelays: !storage.settings.lowPower,
|
||||
autoFetchUserMutelist: !storage.settings.lowPower,
|
||||
clientName: "Lume",
|
||||
});
|
||||
const outboxRelayUrls = normalizeRelayUrlSet(["wss://purplepag.es/"]);
|
||||
|
||||
// use tauri fetch
|
||||
ndk.httpFetch = fetch;
|
||||
const tauriCache = new NDKCacheAdapterTauri(storage);
|
||||
const ndk = new NDK({
|
||||
cacheAdapter: tauriCache,
|
||||
explicitRelayUrls,
|
||||
outboxRelayUrls,
|
||||
enableOutboxModel: !storage.settings.lowPower,
|
||||
autoConnectUserRelays: !storage.settings.lowPower,
|
||||
autoFetchUserMutelist: false, // #TODO: add support mute list
|
||||
clientName: "Lume",
|
||||
});
|
||||
|
||||
// add signer
|
||||
const signer = await initNostrSigner({
|
||||
nsecbunker: storage.settings.nsecbunker,
|
||||
});
|
||||
// use tauri fetch
|
||||
ndk.httpFetch = fetch;
|
||||
|
||||
if (signer) ndk.signer = signer;
|
||||
// add signer
|
||||
const signer = await initNostrSigner({
|
||||
nsecbunker: storage.settings.nsecbunker,
|
||||
});
|
||||
|
||||
// connect
|
||||
await ndk.connect(3000);
|
||||
if (signer) ndk.signer = signer;
|
||||
|
||||
// auth
|
||||
ndk.relayAuthDefaultPolicy = async (relay: NDKRelay, challenge: string) => {
|
||||
const signIn = NDKRelayAuthPolicies.signIn({ ndk });
|
||||
const event = await signIn(relay, challenge).catch((e) =>
|
||||
console.log("auth failed", e),
|
||||
);
|
||||
if (event) {
|
||||
await sendNativeNotification(
|
||||
`You've sign in sucessfully to relay: ${relay.url}`,
|
||||
// connect
|
||||
await ndk.connect(3000);
|
||||
|
||||
// auth
|
||||
ndk.relayAuthDefaultPolicy = async (
|
||||
relay: NDKRelay,
|
||||
challenge: string,
|
||||
) => {
|
||||
const signIn = NDKRelayAuthPolicies.signIn({ ndk });
|
||||
const event = await signIn(relay, challenge).catch((e) =>
|
||||
console.log("auth failed", e),
|
||||
);
|
||||
return event;
|
||||
}
|
||||
};
|
||||
if (event) {
|
||||
await sendNativeNotification(
|
||||
`You've sign in sucessfully to relay: ${relay.url}`,
|
||||
);
|
||||
return event;
|
||||
}
|
||||
};
|
||||
|
||||
setNDK(ndk);
|
||||
setNDK(ndk);
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
}
|
||||
}
|
||||
|
||||
async function initArk() {
|
||||
@ -137,102 +149,68 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
// ark utils
|
||||
const ark = new Ark({ ndk, account: storage.currentUser });
|
||||
|
||||
if (ndk && storage.currentUser) {
|
||||
const user = new NDKUser({ pubkey: storage.currentUser.pubkey });
|
||||
ndk.activeUser = user;
|
||||
try {
|
||||
if (ndk && storage.currentUser) {
|
||||
const user = new NDKUser({ pubkey: storage.currentUser.pubkey });
|
||||
ndk.activeUser = user;
|
||||
|
||||
// update contacts
|
||||
await ark.getUserContacts();
|
||||
// update contacts
|
||||
const contacts = await ark.getUserContacts();
|
||||
|
||||
// subscribe for new activity
|
||||
const activitySub = ndk.subscribe(
|
||||
{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
"#p": [ark.account.pubkey],
|
||||
},
|
||||
{ closeOnEose: false, groupable: false },
|
||||
);
|
||||
|
||||
activitySub.addListener("event", async (event: NDKEvent) => {
|
||||
if (event.pubkey === storage.currentUser.pubkey) return;
|
||||
|
||||
setUnreadActivity((state) => state + 1);
|
||||
const profile = await ark.getUserProfile(event.pubkey);
|
||||
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return await sendNativeNotification(
|
||||
`${
|
||||
profile.displayName || profile.name || "Anon"
|
||||
} has replied to your note`,
|
||||
);
|
||||
case NDKKind.Repost:
|
||||
return await sendNativeNotification(
|
||||
`${
|
||||
profile.displayName || profile.name || "Anon"
|
||||
} has reposted to your note`,
|
||||
);
|
||||
case NDKKind.Zap:
|
||||
return await sendNativeNotification(
|
||||
`${
|
||||
profile.displayName || profile.name || "Anon"
|
||||
} has zapped to your note`,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
if (contacts?.length) {
|
||||
console.log("total contacts: ", contacts.length);
|
||||
for (const pubkey of ark.account.contacts) {
|
||||
await queryClient.prefetchQuery({
|
||||
queryKey: ["user", pubkey],
|
||||
queryFn: async () => {
|
||||
return await ark.getUserProfile(pubkey);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// prefetch activty
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ["activity"],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
pageParam,
|
||||
}: {
|
||||
signal: AbortSignal;
|
||||
pageParam: number;
|
||||
}) => {
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
|
||||
"#p": [ark.account.pubkey],
|
||||
},
|
||||
limit: FETCH_LIMIT,
|
||||
pageParam,
|
||||
signal,
|
||||
});
|
||||
// subscribe for new activity
|
||||
const activitySub = ndk.subscribe(
|
||||
{
|
||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
|
||||
since: Math.floor(Date.now() / 1000),
|
||||
"#p": [ark.account.pubkey],
|
||||
},
|
||||
{ closeOnEose: false, groupable: false },
|
||||
);
|
||||
|
||||
return events;
|
||||
},
|
||||
});
|
||||
activitySub.addListener("event", async (event: NDKEvent) => {
|
||||
if (event.pubkey === storage.currentUser.pubkey) return;
|
||||
|
||||
// prefetch timeline
|
||||
await queryClient.prefetchInfiniteQuery({
|
||||
queryKey: ["timeline-9999"],
|
||||
initialPageParam: 0,
|
||||
queryFn: async ({
|
||||
signal,
|
||||
pageParam,
|
||||
}: {
|
||||
signal: AbortSignal;
|
||||
pageParam: number;
|
||||
}) => {
|
||||
const events = await ark.getInfiniteEvents({
|
||||
filter: {
|
||||
kinds: [NDKKind.Text, NDKKind.Repost],
|
||||
authors: ark.account.contacts,
|
||||
},
|
||||
limit: FETCH_LIMIT,
|
||||
pageParam,
|
||||
signal,
|
||||
});
|
||||
setUnreadActivity((state) => state + 1);
|
||||
const profile = await ark.getUserProfile(event.pubkey);
|
||||
|
||||
return events;
|
||||
},
|
||||
});
|
||||
switch (event.kind) {
|
||||
case NDKKind.Text:
|
||||
return await sendNativeNotification(
|
||||
`${
|
||||
profile.displayName || profile.name || "Anon"
|
||||
} has replied to your note`,
|
||||
);
|
||||
case NDKKind.Repost:
|
||||
return await sendNativeNotification(
|
||||
`${
|
||||
profile.displayName || profile.name || "Anon"
|
||||
} has reposted to your note`,
|
||||
);
|
||||
case NDKKind.Zap:
|
||||
return await sendNativeNotification(
|
||||
`${
|
||||
profile.displayName || profile.name || "Anon"
|
||||
} has zapped to your note`,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(String(e));
|
||||
}
|
||||
|
||||
setArk(ark);
|
||||
@ -250,7 +228,7 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="relative flex items-center justify-center w-screen h-screen bg-neutral-50 dark:bg-neutral-950"
|
||||
className="relative flex items-center justify-center w-screen h-screen"
|
||||
>
|
||||
<div className="flex flex-col items-start max-w-2xl gap-1">
|
||||
<h5 className="font-semibold uppercase">TIP:</h5>
|
||||
|
@ -50,18 +50,7 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
if (!lastEvent) return;
|
||||
return lastEvent.created_at - 1;
|
||||
},
|
||||
initialData: () => {
|
||||
const queryCacheData = queryClient.getQueryState([colKey])
|
||||
?.data as NDKEvent[];
|
||||
if (queryCacheData) {
|
||||
return {
|
||||
pageParams: [undefined, 1],
|
||||
pages: [queryCacheData],
|
||||
};
|
||||
}
|
||||
},
|
||||
select: (data) => data?.pages.flatMap((page) => page),
|
||||
staleTime: 120 * 1000,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
});
|
||||
@ -115,6 +104,17 @@ export function HomeRoute({ colKey }: { colKey: string }) {
|
||||
<div className="w-full flex h-16 items-center justify-center gap-2 px-3 py-1.5">
|
||||
<LoaderIcon className="size-5 animate-spin" />
|
||||
</div>
|
||||
) : !data.length ? (
|
||||
<div className="px-3 mt-3">
|
||||
<EmptyFeed />
|
||||
<Link
|
||||
to="/suggest"
|
||||
className="mt-3 w-full gap-2 inline-flex items-center justify-center text-sm font-medium rounded-lg h-9 bg-blue-500 hover:bg-blue-600 text-white"
|
||||
>
|
||||
<SearchIcon className="size-5" />
|
||||
Find accounts to follow
|
||||
</Link>
|
||||
</div>
|
||||
) : (
|
||||
data.map((item) => renderItem(item))
|
||||
)}
|
||||
|
@ -356,7 +356,7 @@ export function EditorForm() {
|
||||
<Portal>
|
||||
<div
|
||||
ref={ref}
|
||||
className="top-[-9999px] left-[-9999px] absolute z-10 w-[250px] p-1 bg-white border border-neutral-50 dark:border-neutral-900 dark:bg-neutral-950 rounded-lg shadow-lg"
|
||||
className="top-[-9999px] left-[-9999px] absolute z-10 w-[250px] p-2 bg-white border border-neutral-50 dark:border-neutral-900 dark:bg-neutral-950 rounded-xl shadow-lg"
|
||||
>
|
||||
{filters.map((contact, i) => (
|
||||
<button
|
||||
@ -367,13 +367,13 @@ export function EditorForm() {
|
||||
insertMention(editor, contact);
|
||||
setTarget(null);
|
||||
}}
|
||||
className="px-2 py-2 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-900"
|
||||
className="p-2 flex flex-col w-full rounded-lg hover:bg-neutral-100 dark:hover:bg-neutral-900"
|
||||
>
|
||||
<User.Provider pubkey={contact.npub}>
|
||||
<User.Root className="flex items-center gap-2.5">
|
||||
<User.Avatar className="size-10 rounded-lg object-cover shrink-0" />
|
||||
<User.Root className="w-full flex items-center gap-2.5">
|
||||
<User.Avatar className="size-8 rounded-lg object-cover shrink-0" />
|
||||
<div className="flex w-full flex-col items-start">
|
||||
<User.Name className="max-w-[15rem] truncate font-semibold" />
|
||||
<User.Name className="max-w-[8rem] truncate text-sm font-medium" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useArk } from "@lume/ark";
|
||||
import { CheckIcon, LoaderIcon } from "@lume/icons";
|
||||
import { useStorage } from "@lume/storage";
|
||||
import { onboardingAtom } from "@lume/utils";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { motion } from "framer-motion";
|
||||
@ -7,6 +7,7 @@ import { useSetAtom } from "jotai";
|
||||
import { useState } from "react";
|
||||
|
||||
export function OnboardingFinishScreen() {
|
||||
const storage = useStorage();
|
||||
const queryClient = useQueryClient();
|
||||
const setOnboarding = useSetAtom(onboardingAtom);
|
||||
|
||||
@ -15,8 +16,9 @@ export function OnboardingFinishScreen() {
|
||||
const finish = async () => {
|
||||
setLoading(true);
|
||||
|
||||
await queryClient.refetchQueries({ queryKey: ["timeline-9999"] });
|
||||
await queryClient.refetchQueries({ queryKey: ["foryou-9998"] });
|
||||
if (storage.interests) {
|
||||
await queryClient.invalidateQueries({ queryKey: ["foryou-9998"] });
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
setOnboarding({ open: false, newUser: false });
|
||||
|
@ -35,9 +35,10 @@ export function OnboardingInterestScreen() {
|
||||
JSON.stringify({ hashtags }),
|
||||
);
|
||||
|
||||
setLoading(false);
|
||||
|
||||
if (save) return navigate("/finish");
|
||||
if (save) {
|
||||
storage.interests = { hashtags, users: [], words: [] };
|
||||
return navigate("/finish");
|
||||
}
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error(String(e));
|
||||
|
@ -1,15 +1,6 @@
|
||||
import { User, useArk } from "@lume/ark";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
ArrowRightIcon,
|
||||
CancelIcon,
|
||||
LoaderIcon,
|
||||
PlusIcon,
|
||||
} from "@lume/icons";
|
||||
import { cn } from "@lume/utils";
|
||||
import { User } from "@lume/ark";
|
||||
import { ArrowLeftIcon, ArrowRightIcon, LoaderIcon } from "@lume/icons";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { nip19 } from "nostr-tools";
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "sonner";
|
||||
import { WindowVirtualizer } from "virtua";
|
||||
@ -34,7 +25,6 @@ const LUME_USERS = [
|
||||
];
|
||||
|
||||
export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
||||
const ark = useArk();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -51,40 +41,11 @@ export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
||||
},
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [follows, setFollows] = useState<string[]>([]);
|
||||
|
||||
// toggle follow state
|
||||
const toggleFollow = (pubkey: string) => {
|
||||
const arr = follows.includes(pubkey)
|
||||
? follows.filter((i) => i !== pubkey)
|
||||
: [...follows, pubkey];
|
||||
setFollows(arr);
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
if (!follows.length) return navigate("/");
|
||||
|
||||
const publish = await ark.newContactList({
|
||||
tags: follows.map((item) => {
|
||||
if (item.startsWith("npub1"))
|
||||
return ["p", nip19.decode(item).data as string];
|
||||
return ["p", item];
|
||||
}),
|
||||
});
|
||||
|
||||
if (publish) {
|
||||
await queryClient.refetchQueries({ queryKey: ["timeline-9999"] });
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
return navigate("/");
|
||||
await queryClient.refetchQueries({ queryKey });
|
||||
return navigate("/", { replace: true });
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
toast.error(String(e));
|
||||
}
|
||||
};
|
||||
@ -135,30 +96,12 @@ export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
||||
<User.Avatar className="size-10 shrink-0 rounded-lg" />
|
||||
<User.Name className="max-w-[15rem] truncate font-semibold leadning-tight" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => toggleFollow(item.pubkey)}
|
||||
className={cn(
|
||||
"inline-flex h-8 shrink-0 pl-2 pr-2.5 items-center justify-center gap-1 rounded-lg text-sm font-medium",
|
||||
follows.includes(item.pubkey)
|
||||
? "text-red-500 bg-red-100 hover:text-white hover:bg-red-500"
|
||||
: "text-blue-500 bg-blue-100 hover:text-white hover:bg-blue-500",
|
||||
)}
|
||||
>
|
||||
{follows.includes(item.pubkey) ? (
|
||||
<>
|
||||
<CancelIcon className="size-4" />
|
||||
Unfollow
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<PlusIcon className="size-4" />
|
||||
Follow
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
<User.Button
|
||||
target={item.pubkey}
|
||||
className="w-20 h-8 text-sm font-medium bg-neutral-100 dark:bg-neutral-900 hover:bg-neutral-200 dark:hover:bg-neutral-800 rounded-lg inline-flex items-center justify-center"
|
||||
/>
|
||||
</div>
|
||||
<User.About className="break-p text-neutral-800 dark:text-neutral-400 max-w-none select-text whitespace-pre-line" />
|
||||
<User.About className="mt-1 line-clamp-3 text-neutral-800 dark:text-neutral-400 max-w-none select-text" />
|
||||
</div>
|
||||
</User.Root>
|
||||
</User.Provider>
|
||||
@ -170,10 +113,9 @@ export function SuggestRoute({ queryKey }: { queryKey: string[] }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={submit}
|
||||
disabled={loading}
|
||||
className="inline-flex items-center justify-center gap-2 px-6 font-medium shadow-xl shadow-neutral-500/50 text-white transform bg-blue-500 rounded-full active:translate-y-1 w-36 h-11 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed"
|
||||
className="inline-flex items-center justify-center gap-2 px-6 font-medium shadow-xl dark:shadow-none shadow-neutral-500/50 text-white transform bg-blue-500 rounded-full active:translate-y-1 w-44 h-11 hover:bg-blue-600 focus:outline-none disabled:cursor-not-allowed"
|
||||
>
|
||||
Save & Go Back
|
||||
Save & Go back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,7 +26,7 @@ export const NOSTR_EVENTS = [
|
||||
"Nostr:nevent1",
|
||||
];
|
||||
|
||||
// const BITCOINS = ['lnbc', 'bc1p', 'bc1q'];
|
||||
export const BITCOINS = ['lnbc', 'bc1p', 'bc1q'];
|
||||
|
||||
export const IMAGES = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"];
|
||||
|
||||
@ -45,37 +45,6 @@ export const VIDEOS = [
|
||||
|
||||
export const AUDIOS = ["mp3", "ogg", "wav"];
|
||||
|
||||
export const HASHTAGS = [
|
||||
{ hashtag: "#food" },
|
||||
{ hashtag: "#gaming" },
|
||||
{ hashtag: "#nsfw" },
|
||||
{ hashtag: "#bitcoin" },
|
||||
{ hashtag: "#nostr" },
|
||||
{ hashtag: "#nostrdesign" },
|
||||
{ hashtag: "#security" },
|
||||
{ hashtag: "#zap" },
|
||||
{ hashtag: "#LFG" },
|
||||
{ hashtag: "#zapchain" },
|
||||
{ hashtag: "#shitcoin" },
|
||||
{ hashtag: "#plebchain" },
|
||||
{ hashtag: "#nodes" },
|
||||
{ hashtag: "#hodl" },
|
||||
{ hashtag: "#stacksats" },
|
||||
{ hashtag: "#nokyc" },
|
||||
{ hashtag: "#meme" },
|
||||
{ hashtag: "#memes" },
|
||||
{ hashtag: "#memestr" },
|
||||
{ hashtag: "#nostriches" },
|
||||
{ hashtag: "#dev" },
|
||||
{ hashtag: "#anime" },
|
||||
{ hashtag: "#waifu" },
|
||||
{ hashtag: "#manga" },
|
||||
{ hashtag: "#lume" },
|
||||
{ hashtag: "#snort" },
|
||||
{ hashtag: "#damus" },
|
||||
{ hashtag: "#primal" },
|
||||
];
|
||||
|
||||
export const COL_TYPES = {
|
||||
default: 0,
|
||||
user: 1,
|
||||
@ -173,7 +142,6 @@ export const TOPICS = [
|
||||
"#pcgaming",
|
||||
"#nintendo",
|
||||
"#switch",
|
||||
"#pubg",
|
||||
"#esports",
|
||||
"#gameoftheyear",
|
||||
"#darksoul",
|
||||
@ -326,7 +294,6 @@ export const TOPICS = [
|
||||
"#fashion",
|
||||
"#travel",
|
||||
"#photoshoot",
|
||||
"#nature",
|
||||
"#naturephotography",
|
||||
"#smile",
|
||||
"#style",
|
||||
|
Loading…
Reference in New Issue
Block a user