From ca6f90cb4501e6213beb6abc369378f14a88c33f Mon Sep 17 00:00:00 2001 From: Abhishek7Tech Date: Mon, 17 Aug 2026 14:15:09 +0530 Subject: [PATCH 1/2] fix(bug): show collection in dicover section on unfollowed or reload --- .../Collections/CollectionCard/CollectionCard.tsx | 8 ++++++++ .../CollectionsSections/CollectionsSections.tsx | 10 ++++++++-- .../DiscoverCollections/DiscoverCollections.tsx | 9 +++++++-- .../FollowedCollections/FollowedCollections.tsx | 12 ++++++++++-- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/components/organisms/Collections/CollectionCard/CollectionCard.tsx b/src/components/organisms/Collections/CollectionCard/CollectionCard.tsx index 31eddfa41..baaaa322e 100644 --- a/src/components/organisms/Collections/CollectionCard/CollectionCard.tsx +++ b/src/components/organisms/Collections/CollectionCard/CollectionCard.tsx @@ -62,6 +62,7 @@ interface CollectionCardProps { interactiveActions?: boolean; /** Show the owner Delete action. Opt in only from the My Collections overview. */ showDeleteAction?: boolean; + unfollowCollectionHandler?: (id: string) => void; } /** @@ -91,6 +92,7 @@ export function CollectionCard({ presentation = 'landing', interactiveActions = true, showDeleteAction = false, + unfollowCollectionHandler, }: CollectionCardProps) { const compositeId = buildCompositeId({ pubky: authorPubky, id: postId }); const isMobile = useIsMobile(); @@ -133,6 +135,7 @@ export function CollectionCard({ isWideLayout={isWideLayout} interactiveActions={interactiveActions} showDeleteAction={showDeleteAction} + unfollowCollectionHandler={unfollowCollectionHandler} /> ); } @@ -149,6 +152,7 @@ interface CollectionCardContentProps { isWideLayout: boolean; interactiveActions: boolean; showDeleteAction: boolean; + unfollowCollectionHandler?: (id: string) => void; } function CollectionCardContent({ @@ -163,6 +167,7 @@ function CollectionCardContent({ isWideLayout, interactiveActions, showDeleteAction, + unfollowCollectionHandler, }: CollectionCardContentProps) { const isEmbed = presentation === 'embed'; const showTagAddButton = interactiveActions && !isEmbed; @@ -222,6 +227,9 @@ function CollectionCardContent({ requireAuth(() => { void toggle(); }); + if (isBookmarked) { + unfollowCollectionHandler?.(compositeId); + } }; // Collection-specific toast copy so success / failure reads as "Collection diff --git a/src/components/organisms/Collections/CollectionsSections/CollectionsSections.tsx b/src/components/organisms/Collections/CollectionsSections/CollectionsSections.tsx index 0b8930c82..447978fc2 100644 --- a/src/components/organisms/Collections/CollectionsSections/CollectionsSections.tsx +++ b/src/components/organisms/Collections/CollectionsSections/CollectionsSections.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useState } from 'react'; import { Container } from '@/atoms/Container/Container'; import { cn } from '@/libs/utils/utils'; import { DiscoverCollections } from '@/organisms/Collections/DiscoverCollections/DiscoverCollections'; @@ -28,16 +29,21 @@ export function CollectionsSections({ className }: CollectionsSectionsProps) { const hasHydrated = useAuthStore((state) => state.hasHydrated); const currentUserPubky = useAuthStore((state) => state.currentUserPubky); const showPersonalSections = hasHydrated && Boolean(currentUserPubky); + const [unfollowedId, setUnfollowedId] = useState(null); + + function unfollowedCollectionIdHandler(id: string) { + setUnfollowedId(id); + } return ( {showPersonalSections ? ( <> - + ) : null} - + ); } diff --git a/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx b/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx index 289977bf2..254e587f0 100644 --- a/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx +++ b/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx @@ -79,7 +79,7 @@ const EMPTY_CURSOR: DiscoverCursor = { lastPostId: undefined, streamTail: 0 }; * raw posts and filtering removed them all), we surface a toast so the * click gets feedback instead of silently rendering nothing. */ -export function DiscoverCollections() { +export function DiscoverCollections({ unfollowedId }: { unfollowedId?: string | null }) { const { toast } = useToast(); // The stream layer filters own collections against the viewer read from the // auth store, so a viewer switch must reset and refetch (effect dep below). @@ -102,6 +102,12 @@ export function DiscoverCollections() { const visibleIdsRef = useRef([]); visibleIdsRef.current = visibleIds; + useEffect(() => { + if (unfollowedId && !visibleIdsRef.current.includes(unfollowedId)) { + setVisibleIds([unfollowedId, ...visibleIdsRef.current]); + } + }, [unfollowedId]); + // Cancellation token for the in-flight initial fetch. When the effect // re-fires (StrictMode double-invoke, or genuine viewer switch), the old // fetch's closure sees `cancelled.current === true` and skips its state @@ -146,7 +152,6 @@ export function DiscoverCollections() { limit: COLLECTIONS_SECTION_PAGE_SIZE, }); if (token?.cancelled) return; - // `nextPageIds` is already post-filter; `nextCursor` is the raw skip // offset the stream layer consumed to produce it. Dedup is defensive // only — the stream layer's cursor accounting should prevent overlap. diff --git a/src/components/organisms/Collections/FollowedCollections/FollowedCollections.tsx b/src/components/organisms/Collections/FollowedCollections/FollowedCollections.tsx index 97e04ffd1..b019a4955 100644 --- a/src/components/organisms/Collections/FollowedCollections/FollowedCollections.tsx +++ b/src/components/organisms/Collections/FollowedCollections/FollowedCollections.tsx @@ -52,7 +52,7 @@ const EMPTY_IDS: string[] = []; * Live-reactive: a Follow / Unfollow performed anywhere in the app * pushes a card into / out of this section without a reload. */ -export function FollowedCollections() { +export function FollowedCollections({ unfollowHandler }: { unfollowHandler?: (id: string) => void }) { const { toast } = useToast(); // Gate the seed fetch on auth hydration. `StreamPostsController.getOrFetchStreamSlice` // reads `viewerId` from the auth store synchronously, and the bookmarks-collection @@ -196,7 +196,15 @@ export function FollowedCollections() { // collection. Seed the bookmark hook so the CTA renders as // "Unfollow" on the first paint instead of briefly flashing // "Follow" while the async existence check resolves. - return ; + return ( + + ); })} )} From ee6a87c86ab086fa6ac8885495b9cc8b33e63d76 Mon Sep 17 00:00:00 2001 From: Abhishek7Tech Date: Mon, 17 Aug 2026 14:39:14 +0530 Subject: [PATCH 2/2] fix(bug): show collection in dicover section on unfollowed or reload --- .../Collections/DiscoverCollections/DiscoverCollections.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx b/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx index 254e587f0..6f265e0bd 100644 --- a/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx +++ b/src/components/organisms/Collections/DiscoverCollections/DiscoverCollections.tsx @@ -103,10 +103,11 @@ export function DiscoverCollections({ unfollowedId }: { unfollowedId?: string | visibleIdsRef.current = visibleIds; useEffect(() => { + if (loading || loadingMore) return; if (unfollowedId && !visibleIdsRef.current.includes(unfollowedId)) { setVisibleIds([unfollowedId, ...visibleIdsRef.current]); } - }, [unfollowedId]); + }, [unfollowedId, loading, loadingMore]); // Cancellation token for the in-flight initial fetch. When the effect // re-fires (StrictMode double-invoke, or genuine viewer switch), the old