From b31b9e53bb1e9303ec4b7277dd7aeed386afc4e1 Mon Sep 17 00:00:00 2001 From: whey.party Date: Sun, 18 Jan 2026 15:17:36 +0000 Subject: [PATCH] Feeds page --- src/routes/feeds.tsx | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file(s) changed, 145 insertion(s)(+), 3 deletion(s)(-) diff --git a/src/routes/feeds.tsx b/src/routes/feeds.tsx --- a/src/routes/feeds.tsx +++ b/src/routes/feeds.tsx @@ -1,12 +1,52 @@ -import { createFileRoute } from "@tanstack/react-router"; +import * as ATPAPI from "@atproto/api"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import { useAtom } from "jotai"; +import * as React from "react"; import { Header } from "~/components/Header"; +import { useAuth } from "~/providers/UnifiedAuthProvider"; +import { imgCDNAtom, quickAuthAtom } from "~/utils/atoms"; +import { + useQueryArbitrary, + useQueryIdentity, + useQueryPreferences, +} from "~/utils/useQuery"; export const Route = createFileRoute("/feeds")({ component: Feeds, }); export function Feeds() { + const { agent, status } = useAuth(); + const [quickAuth] = useAtom(quickAuthAtom); + const isAuthRestoring = quickAuth ? status === "loading" : false; + + const identityresultmaybe = useQueryIdentity( + !isAuthRestoring ? agent?.did : undefined, + ); + const identity = identityresultmaybe?.data; + + const prefsresultmaybe = useQueryPreferences({ + agent: !isAuthRestoring ? (agent ?? undefined) : undefined, + pdsUrl: !isAuthRestoring ? identity?.pds : undefined, + }); + const prefs = prefsresultmaybe?.data; + + const savedFeeds = React.useMemo(() => { + const savedFeedsPref = prefs?.preferences?.find( + (p: any) => p?.$type === "app.bsky.actor.defs#savedFeedsPrefV2", + ); + return savedFeedsPref?.items || []; + }, [prefs]); + + const pinnedFeeds = React.useMemo(() => { + return savedFeeds.filter((feed: any) => feed.pinned); + }, [savedFeeds]); + + const nonPinnedFeeds = React.useMemo(() => { + return savedFeeds.filter((feed: any) => !feed.pinned); + }, [savedFeeds]); + return (
- Feeds page (coming soon) +
+ {pinnedFeeds.length > 0 && ( +
+

Pinned Feeds

+
+ {pinnedFeeds.map((feed: any) => ( + + ))} +
+
+ )} + + {nonPinnedFeeds.length > 0 && ( +
+

Saved Feeds

+
+ {nonPinnedFeeds.map((feed: any) => ( + + ))} +
+
+ )} + + {savedFeeds.length === 0 && ( +
+

No feeds saved yet.

+

+ Save feeds from the home page to see them here. +

+
+ )} +
+ ); +} + +function FeedItem({ feedUri }: { feedUri: string }) { + const { data: feedData } = useQueryArbitrary(feedUri); + const feed = feedData?.value as ATPAPI.AppBskyFeedGenerator.Record; + const [imgcdn] = useAtom(imgCDNAtom); + let aturi: ATPAPI.AtUri | null = null; + try { + aturi = new ATPAPI.AtUri(feedUri); + } catch (err) { + // todo terrible hack lmaoo (hack type: forcing following feed to fallback to rinds fresh feed) + aturi = new ATPAPI.AtUri("at://did:plc:mn45tewwnse5btfftvd3powc/app.bsky.feed.generator/rinds"); + } + + function getAvatarUrl() { + const link = feed?.avatar?.ref?.["$link"]; + if (!link) return null; + return `https://${imgcdn}/img/avatar/plain/${aturi?.host}/${link}@jpeg`; + } + + const avatarUrl = getAvatarUrl(); + + return ( + { + e.stopPropagation(); + }} + //disabled={feedUri === "following"} + > +
+
+ {feed?.displayName { + const target = e.target as HTMLImageElement; + target.onerror = null; + target.src = "/defaultpfp.png"; + }} + /> +
+

+ {feed?.displayName || feedUri.split("/").pop()} +

+

+ {feedUri === "following" ? "(not implemented, if clicked will open an alternative)" : feed?.description || "No description"} +

+
+
+
+ + + +
+
+ ); } -- tangled.sh