diff --git a/public/images/screenshot.png b/public/images/screenshot.png index f6fb27fa..18609ce6 100644 Binary files a/public/images/screenshot.png and b/public/images/screenshot.png differ diff --git a/src/app/opengraph-image.png b/src/app/opengraph-image.png index 5121d835..92aad174 100644 Binary files a/src/app/opengraph-image.png and b/src/app/opengraph-image.png differ diff --git a/src/app/twitter-image.png b/src/app/twitter-image.png index 79a6f2a6..92aad174 100644 Binary files a/src/app/twitter-image.png and b/src/app/twitter-image.png differ diff --git a/src/components/contentDisplay/feedHeader/FeedHeader.tsx b/src/components/contentDisplay/feedHeader/FeedHeader.tsx index bc0890c2..46fa4ab4 100644 --- a/src/components/contentDisplay/feedHeader/FeedHeader.tsx +++ b/src/components/contentDisplay/feedHeader/FeedHeader.tsx @@ -1,12 +1,13 @@ "use client"; import Image from "next/image"; -import useFeedInfo from "@/lib/hooks/bsky/feed/useFeedInfo"; +import useFeedInfo, { feedInfoKey } from "@/lib/hooks/bsky/feed/useFeedInfo"; import FallbackFeed from "@/assets/images/fallbackFeed.png"; import Button from "@/components/actions/button/Button"; -import { useEffect, useLayoutEffect, useState } from "react"; +import { useLayoutEffect, useState } from "react"; import { - getSavedFeeds, + likeFeed, + unlikeFeed, togglePinFeed, toggleSaveFeed, } from "@/lib/api/bsky/feed"; @@ -15,7 +16,7 @@ import { useRouter } from "next/navigation"; import FeedHeaderSkeleton from "./FeedHeaderSkeleton"; import { useQueryClient } from "@tanstack/react-query"; import { savedFeedsQueryKey } from "@/containers/settings/myFeedsContainer/MyFeedsContainer"; -import { BiSolidTrash } from "react-icons/bi"; +import { BiHeart, BiSolidTrash } from "react-icons/bi"; import { BiSolidBookmarkAlt } from "react-icons/bi"; import { BiPlus } from "react-icons/bi"; import { BiSolidHeart } from "react-icons/bi"; @@ -31,6 +32,9 @@ export default function FeedHeader(props: Props) { const agent = useAgent(); const [isSaved, setIsSaved] = useState(null); const [isPinned, setIsPinned] = useState(null); + const [isLiked, setIsLiked] = useState(null); + const [likeUri, setLikeUri] = useState(undefined); + const [likeCount, setLikeCount] = useState(0); const queryClient = useQueryClient(); const { feedInfo, @@ -44,6 +48,8 @@ export default function FeedHeader(props: Props) { if (feedInfo) { setIsSaved(feedInfo.isSaved); setIsPinned(feedInfo.isPinned); + setIsLiked(feedInfo.isLiked); + setLikeUri(feedInfo.view.viewer?.like); } }, [feedInfo]); @@ -79,10 +85,34 @@ export default function FeedHeader(props: Props) { } }; + const toggleLike = async () => { + if (!agent) return; + setIsLiked((prev) => !prev); + if (!likeUri && feedInfo) { + try { + const like = await likeFeed( + agent, + feedInfo?.view.uri, + feedInfo?.view.cid + ); + setLikeUri(like?.uri); + } catch (err) { + setIsLiked(false); + } + } else if (likeUri && feedInfo) { + try { + await unlikeFeed(agent, likeUri); + setLikeUri(undefined); + } catch (err) { + setIsLiked(true); + } + } + }; + return ( <> {isFetchingFeedInfo && } - {feedInfo && ( + {!isFetchingFeedInfo && feedInfo && ( <>
@@ -124,6 +154,14 @@ export default function FeedHeader(props: Props) { }`} /> +
)} @@ -132,7 +170,7 @@ export default function FeedHeader(props: Props) {

- {feedInfo.view.likeCount ?? 0} + {feedInfo.view.likeCount}
diff --git a/src/components/contentDisplay/feedHeader/FeedHeaderSkeleton.tsx b/src/components/contentDisplay/feedHeader/FeedHeaderSkeleton.tsx index 926e8053..09019c6d 100644 --- a/src/components/contentDisplay/feedHeader/FeedHeaderSkeleton.tsx +++ b/src/components/contentDisplay/feedHeader/FeedHeaderSkeleton.tsx @@ -1,6 +1,6 @@ export default function FeedHeaderSkeleton() { return ( -
+
@@ -12,6 +12,7 @@ export default function FeedHeaderSkeleton() {
+
diff --git a/src/components/navigational/appBar/AppBar.tsx b/src/components/navigational/appBar/AppBar.tsx index 0005e523..565c4f0a 100644 --- a/src/components/navigational/appBar/AppBar.tsx +++ b/src/components/navigational/appBar/AppBar.tsx @@ -6,7 +6,7 @@ import { useScrollContext } from "@/app/providers/scroll"; import useAgent from "@/lib/hooks/bsky/useAgent"; import { getUnreadNotificationsCount } from "@/lib/api/bsky/notification"; import { useQuery } from "@tanstack/react-query"; -import { BiCog, BiHash, BiHome, BiSolidCog, BiSolidHome } from "react-icons/bi"; +import { BiCloud, BiCog, BiHome, BiSolidCloud, BiSolidCog, BiSolidHome } from "react-icons/bi"; import { PiMagnifyingGlassBold, PiMagnifyingGlassFill } from "react-icons/pi"; import { FaRegBell } from "react-icons/fa6"; import { FaBell } from "react-icons/fa"; @@ -50,8 +50,8 @@ export default function AppBar() { /> } - activeIcon={} + icon={} + activeIcon={} title="Feeds" isActive={pathname === "/dashboard/feeds"} /> diff --git a/src/components/navigational/navbar/Navbar.tsx b/src/components/navigational/navbar/Navbar.tsx index 1664e6e3..3388ab68 100644 --- a/src/components/navigational/navbar/Navbar.tsx +++ b/src/components/navigational/navbar/Navbar.tsx @@ -8,12 +8,13 @@ import { getUnreadNotificationsCount } from "@/lib/api/bsky/notification"; import useAgent from "@/lib/hooks/bsky/useAgent"; import { BiCog, - BiHash, + BiCloud, BiHome, BiSolidCog, BiSolidHome, BiSolidUser, BiUser, + BiSolidCloud, } from "react-icons/bi"; import { PiMagnifyingGlassBold, PiMagnifyingGlassFill } from "react-icons/pi"; import { FaBell, FaRegBell } from "react-icons/fa6"; @@ -52,8 +53,8 @@ export default function Navbar() { /> } - activeIcon={} + icon={} + activeIcon={} title="Feeds" isActive={pathname === "/dashboard/feeds"} className="sm:m-0" diff --git a/src/containers/settings/myFeedsContainer/MyFeedsContainer.tsx b/src/containers/settings/myFeedsContainer/MyFeedsContainer.tsx index bd58398a..a78a3c99 100644 --- a/src/containers/settings/myFeedsContainer/MyFeedsContainer.tsx +++ b/src/containers/settings/myFeedsContainer/MyFeedsContainer.tsx @@ -79,7 +79,7 @@ function FeedItem(props: FeedItemProps) { export default function MyFeedsContainer() { const agent = useAgent(); const { status, data, error, isLoading, isFetching } = useQuery({ - queryKey: ["savedFeeds"], + queryKey: savedFeedsQueryKey, queryFn: () => getSavedFeeds(agent), }); diff --git a/src/containers/settings/settingsContainer/SettingsContainer.tsx b/src/containers/settings/settingsContainer/SettingsContainer.tsx index 8b690a4c..625811df 100644 --- a/src/containers/settings/settingsContainer/SettingsContainer.tsx +++ b/src/containers/settings/settingsContainer/SettingsContainer.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { FaSlidersH } from "react-icons/fa"; import { ImBubbles2 } from "react-icons/im"; import { - BiHash, + BiCloud, BiLogoGithub, BiSolidCheckCircle, BiSolidEnvelope, @@ -98,7 +98,7 @@ export default async function SettingsContainer() { href="/dashboard/settings/my-feeds" className="flex items-center gap-2 p-3 border border-x-0 md:border-x md:first:rounded-t-2xl md:last:rounded-b-2xl last:border-b even:[&:not(:last-child)]:border-b-0 odd:[&:not(:last-child)]:border-b-0 hover:bg-neutral-50" > - + My Feeds
diff --git a/src/lib/api/bsky/feed/index.ts b/src/lib/api/bsky/feed/index.ts index 519a77a6..23568a39 100644 --- a/src/lib/api/bsky/feed/index.ts +++ b/src/lib/api/bsky/feed/index.ts @@ -88,6 +88,15 @@ export const toggleSaveFeed = async (agent: BskyAgent, feed: string) => { }); }; +export const likeFeed = async (agent: BskyAgent, uri: string, cid: string) => { + const like = await agent.like(uri, cid); + return like; +}; + +export const unlikeFeed = async (agent: BskyAgent, likeUri: string) => { + await agent.deleteLike(likeUri); +}; + export const getTimeline = async (agent: BskyAgent, cursor?: string) => { const timeline = await agent.getTimeline({ cursor: cursor }); return timeline; diff --git a/src/lib/hooks/bsky/feed/useFeedInfo.tsx b/src/lib/hooks/bsky/feed/useFeedInfo.tsx index b2fb6d33..13505983 100644 --- a/src/lib/hooks/bsky/feed/useFeedInfo.tsx +++ b/src/lib/hooks/bsky/feed/useFeedInfo.tsx @@ -2,13 +2,13 @@ import useAgent from "../useAgent"; import { getFeedInfo, getSavedFeeds } from "../../../api/bsky/feed"; import { useQuery } from "@tanstack/react-query"; -export const useFeedInfoKey = (feed: string) => ["feedInfo", feed]; +export const feedInfoKey = (feed: string) => ["feedInfo", feed]; export default function useFeedInfo(feed: string) { - const agent = useAgent(); + const agent = useAgent(); const { data, isLoading, isFetching, isRefetching, error } = useQuery({ - queryKey: useFeedInfoKey(feed), + queryKey: feedInfoKey(feed), queryFn: async () => { const feedInfo = await getFeedInfo(agent, feed); const savedFeeds = await getSavedFeeds(agent); @@ -16,7 +16,8 @@ export default function useFeedInfo(feed: string) { const isPinned = savedFeeds.some( (savedFeed) => savedFeed.uri === feed && savedFeed.pinned ); - return { ...feedInfo, isSaved, isPinned }; + const isLiked = feedInfo.view.viewer?.like !== null ? true : false; + return { ...feedInfo, isSaved, isPinned, isLiked }; }, });