summaryrefslogtreecommitdiff
path: root/gui/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/components')
-rw-r--r--gui/src/components/feed/PostList.tsx17
-rw-r--r--gui/src/components/nostr/Feed.tsx115
-rw-r--r--gui/src/components/post/Footer.tsx1
-rw-r--r--gui/src/components/post/Header.tsx10
-rw-r--r--gui/src/components/post/Post.tsx4
-rw-r--r--gui/src/components/post/wrappers/NostrIcon.tsx15
6 files changed, 147 insertions, 15 deletions
diff --git a/gui/src/components/feed/PostList.tsx b/gui/src/components/feed/PostList.tsx
index 0d01bd2..12b58b4 100644
--- a/gui/src/components/feed/PostList.tsx
+++ b/gui/src/components/feed/PostList.tsx
@@ -1,10 +1,12 @@
import TrillPost from "@/components/post/Post";
import type { FC } from "@/types/trill";
+import useLocalState from "@/state/state";
// import { useEffect } from "react";
// import { useQueryClient } from "@tanstack/react-query";
// import { toFull } from "../thread/helpers";
function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) {
+ const { profiles } = useLocalState((s) => ({ profiles: s.profiles }));
// const qc = useQueryClient();
// useEffect(() => {
// Object.values(data.feed).forEach((poast) => {
@@ -25,9 +27,18 @@ function TrillFeed({ data, refetch }: { data: FC; refetch: Function }) {
.sort()
.reverse()
.slice(0, 50)
- .map((i) => (
- <TrillPost key={i} poast={data.feed[i]} refetch={refetch} />
- ))}
+ .map((i) => {
+ const poast = data.feed[i];
+ const profile = profiles.get(poast.author);
+ return (
+ <TrillPost
+ key={i}
+ poast={poast}
+ profile={profile}
+ refetch={refetch}
+ />
+ );
+ })}
</>
);
}
diff --git a/gui/src/components/nostr/Feed.tsx b/gui/src/components/nostr/Feed.tsx
new file mode 100644
index 0000000..0e74cea
--- /dev/null
+++ b/gui/src/components/nostr/Feed.tsx
@@ -0,0 +1,115 @@
+import PostList from "@/components/feed/PostList";
+import useLocalState from "@/state/state";
+import spinner from "@/assets/triangles.svg";
+import { useState } from "react";
+import { eventsToFc } from "@/logic/nostrill";
+import Icon from "@/components/Icon";
+import toast from "react-hot-toast";
+
+export default function Nostr() {
+ const { nostrFeed, api, relays } = useLocalState((s) => ({
+ nostrFeed: s.nostrFeed,
+ api: s.api,
+ relays: s.relays,
+ }));
+ console.log({ relays });
+ const [isSyncing, setIsSyncing] = useState(false);
+ const feed = eventsToFc(nostrFeed);
+ console.log({ feed });
+ const refetch = () => feed;
+
+ const handleResync = async () => {
+ if (!api) return;
+
+ setIsSyncing(true);
+ try {
+ await api.syncRelays();
+ toast.success("Nostr feed sync initiated");
+ } catch (error) {
+ toast.error("Failed to sync Nostr feed");
+ console.error("Sync error:", error);
+ } finally {
+ setIsSyncing(false);
+ }
+ };
+
+ if (Object.keys(relays).length === 0)
+ return (
+ <div className="nostr-empty-state">
+ <div className="empty-content">
+ <Icon name="nostr" size={48} color="textMuted" />
+ <h3>No Nostr Relays Set Up</h3>
+ <p>
+ You haven't set any Nostr Relays to sync data from. You can do so in
+ the Settings page.
+ </p>
+ <p>
+ If you don't know of any, we recommend the following public relays:
+ </p>
+ <ul>
+ <li>wss://nos.lol</li>
+ <li>wss://relay.damus.io</li>
+ </ul>
+ </div>
+ </div>
+ );
+ // Show empty state with resync option when no feed data
+ if (!feed || !feed.feed || Object.keys(feed.feed).length === 0) {
+ return (
+ <div className="nostr-empty-state">
+ <div className="empty-content">
+ <Icon name="nostr" size={48} color="textMuted" />
+ <h3>No Nostr Posts</h3>
+ <p>
+ Your Nostr feed appears to be empty. Try syncing with your relays to
+ fetch the latest posts.
+ </p>
+ <button
+ onClick={handleResync}
+ disabled={isSyncing}
+ className="resync-btn"
+ >
+ {isSyncing ? (
+ <>
+ <img src={spinner} alt="Loading" className="btn-spinner" />
+ Syncing...
+ </>
+ ) : (
+ <>
+ <Icon name="settings" size={16} />
+ Sync Relays
+ </>
+ )}
+ </button>
+ </div>
+ </div>
+ );
+ }
+
+ // Show feed with resync button in header
+ return (
+ <div className="nostr-feed">
+ <div className="nostr-header">
+ <div className="feed-info">
+ <h4>Nostr Feed</h4>
+ <span className="post-count">
+ {Object.keys(feed.feed).length} posts
+ </span>
+ </div>
+ <button
+ onClick={handleResync}
+ disabled={isSyncing}
+ className="resync-btn-small"
+ title="Sync with Nostr relays"
+ >
+ {isSyncing ? (
+ <img src={spinner} alt="Loading" className="btn-spinner-small" />
+ ) : (
+ <Icon name="settings" size={16} />
+ )}
+ </button>
+ </div>
+ <PostList data={feed} refetch={refetch} />
+ </div>
+ );
+}
diff --git a/gui/src/components/post/Footer.tsx b/gui/src/components/post/Footer.tsx
index 87f45f3..41752fc 100644
--- a/gui/src/components/post/Footer.tsx
+++ b/gui/src/components/post/Footer.tsx
@@ -41,7 +41,6 @@ function Footer({ poast, refetch }: PostProps) {
// Scroll to top where composer is located
window.scrollTo({ top: 0, behavior: "smooth" });
}
- console.log({ poast });
const childrenCount = poast.children
? poast.children.length
? poast.children.length
diff --git a/gui/src/components/post/Header.tsx b/gui/src/components/post/Header.tsx
index 0dfd5e4..b0822b4 100644
--- a/gui/src/components/post/Header.tsx
+++ b/gui/src/components/post/Header.tsx
@@ -1,11 +1,9 @@
import { date_diff } from "@/logic/utils";
import type { PostProps } from "./Post";
import { useLocation } from "wouter";
-import useLocalState from "@/state/state";
function Header(props: PostProps) {
const [_, navigate] = useLocation();
- const profiles = useLocalState((s) => s.profiles);
- const profile = profiles.get(props.poast.author);
+ const { profile } = props;
// console.log("profile", profile);
// console.log(props.poast.author.length, "length");
function go(e: React.MouseEvent) {
@@ -21,13 +19,11 @@ function Header(props: PostProps) {
const name = profile ? (
profile.name
) : (
- <div className="name cp">
- <p className="p-only">{poast.author}</p>
- </div>
+ <p className="p-only">{poast.author}</p>
);
return (
<header>
- <div className="author flex-align" role="link" onMouseUp={go}>
+ <div className="cp author flex-align name" role="link" onMouseUp={go}>
{name}
</div>
<div role="link" onMouseUp={openThread} className="date">
diff --git a/gui/src/components/post/Post.tsx b/gui/src/components/post/Post.tsx
index 2965040..2d9a09a 100644
--- a/gui/src/components/post/Post.tsx
+++ b/gui/src/components/post/Post.tsx
@@ -22,7 +22,7 @@ export interface PostProps {
profile?: UserProfile;
}
function Post(props: PostProps) {
- console.log("post", props);
+ // console.log("post", props);
const { poast } = props;
if (!poast || poast.contents === null) {
return null;
@@ -60,7 +60,7 @@ function TrillPost(props: PostProps) {
setModal(<ShipModal ship={poast.author} />);
}
const avatar = profile ? (
- <div className="avatar cp" role="link" onMouseUp={openModal}>
+ <div className="avatar sigil cp" role="link" onMouseUp={openModal}>
<img src={profile.picture} />
</div>
) : (
diff --git a/gui/src/components/post/wrappers/NostrIcon.tsx b/gui/src/components/post/wrappers/NostrIcon.tsx
index 30fbfe9..f39d689 100644
--- a/gui/src/components/post/wrappers/NostrIcon.tsx
+++ b/gui/src/components/post/wrappers/NostrIcon.tsx
@@ -2,14 +2,23 @@ import Icon from "@/components/Icon";
import useLocalState from "@/state/state";
import toast from "react-hot-toast";
import type { Poast } from "@/types/trill";
+import { generateNevent } from "@/logic/nostr";
export default function ({ poast }: { poast: Poast }) {
const { relays, api } = useLocalState((s) => ({
relays: s.relays,
api: s.api,
}));
- async function sendToRelay(e: React.MouseEvent) {
+ async function handleClick(e: React.MouseEvent) {
e.stopPropagation();
+ if (poast.event) {
+ const nevent = generateNevent(poast.event);
+ console.log({ nevent });
+ const href = `https://primal.net/e/${nevent}`;
+ window.open(href, "_blank");
+ } else sendToRelay(e);
+ }
+ async function sendToRelay(e: React.MouseEvent) {
//
const urls = Object.keys(relays);
await api!.relayPost(poast.host, poast.id, urls);
@@ -18,8 +27,10 @@ export default function ({ poast }: { poast: Poast }) {
// TODO round up all helpers
return (
- <div className="icon" role="link" onMouseUp={sendToRelay}>
+ <div className="icon" role="link" onMouseUp={handleClick}>
<Icon name="nostr" size={20} title="relay to nostr" />
</div>
);
}
+
+// npub1w8k2hk9kkv653cr4luqmx9tglldpn59vy7yqvlvex2xxmeygt96s4dlh8p