From 28e6642fa6f93fb562ce4ec2759d22ea8abbace2 Mon Sep 17 00:00:00 2001 From: senke Date: Sat, 21 Feb 2026 05:31:12 +0100 Subject: [PATCH] feat(social): GET /social/explore, explore tab, feed filters all/following/groups (S1.5, S1.6) --- .../social/pages/social-page/SocialView.tsx | 35 ++++---- .../pages/social-page/SocialViewExplore.tsx | 83 +++++++++++++++++++ .../pages/social-page/SocialViewFeed.tsx | 24 +++++- .../pages/social-page/SocialViewSidebar.tsx | 12 ++- .../social/pages/social-page/types.ts | 2 +- .../social/pages/social-page/useSocialView.ts | 6 +- apps/web/src/mocks/handlers-social.ts | 14 ++++ apps/web/src/services/socialService.ts | 13 ++- .../internal/api/routes_social.go | 7 +- .../internal/core/social/service.go | 13 ++- veza-backend-api/internal/handlers/social.go | 39 ++++++++- 11 files changed, 219 insertions(+), 29 deletions(-) create mode 100644 apps/web/src/features/social/pages/social-page/SocialViewExplore.tsx diff --git a/apps/web/src/features/social/pages/social-page/SocialView.tsx b/apps/web/src/features/social/pages/social-page/SocialView.tsx index 84f1914c6..581cfa1ea 100644 --- a/apps/web/src/features/social/pages/social-page/SocialView.tsx +++ b/apps/web/src/features/social/pages/social-page/SocialView.tsx @@ -1,6 +1,7 @@ import { useSocialView } from './useSocialView'; import { SocialViewSidebar } from './SocialViewSidebar'; import { SocialViewFeed } from './SocialViewFeed'; +import { SocialViewExplore } from './SocialViewExplore'; import { SocialViewTrending } from './SocialViewTrending'; import { SocialViewSkeleton } from './SocialViewSkeleton'; import { SocialViewError } from './SocialViewError'; @@ -8,16 +9,12 @@ import type { SocialViewProps } from './types'; import type { SocialTabKey } from './types'; export function SocialView({ onViewProfile }: SocialViewProps) { - const { activeTab, setActiveTab, feedItems, loading, error, retry, playTrack, loadMore, hasMore, isLoadingMore } = useSocialView(); + const { activeTab, setActiveTab, feedFilter, setFeedFilter, feedItems, loading, error, retry, playTrack, loadMore, hasMore, isLoadingMore } = useSocialView(); - if (loading) { + if (loading && activeTab === 'feed') { return ; } - if (error) { - return ; - } - return (
onViewProfile(null)} /> - + {activeTab === 'explore' ? ( + + ) : error ? ( +
+ +
+ ) : ( + + )}
diff --git a/apps/web/src/features/social/pages/social-page/SocialViewExplore.tsx b/apps/web/src/features/social/pages/social-page/SocialViewExplore.tsx new file mode 100644 index 000000000..e341194be --- /dev/null +++ b/apps/web/src/features/social/pages/social-page/SocialViewExplore.tsx @@ -0,0 +1,83 @@ +import { useQuery } from '@tanstack/react-query'; +import { Card } from '@/components/ui/card'; +import { Hash, Users } from 'lucide-react'; +import { Skeleton } from '@/components/ui/skeleton'; +import { socialService } from '@/services/socialService'; + +export function SocialViewExplore() { + const { data, isLoading, isError } = useQuery({ + queryKey: ['social', 'explore'], + queryFn: () => socialService.getExplore(), + staleTime: 5 * 60 * 1000, + }); + + const trending = data?.trending ?? []; + const suggestedUsers = data?.suggestedUsers ?? []; + + return ( +
+

Explore

+

Trending hashtags and suggested users

+ + +

+ Trending +

+ {isLoading ? ( +
+ {[1, 2, 3, 4, 5].map((i) => ( + + ))} +
+ ) : ( +
+ {trending.length > 0 ? ( + trending.map((t) => ( + + {t.tag} ({t.count}) + + )) + ) : ( +

No trending tags yet.

+ )} +
+ )} +
+ + +

+ Suggested Users +

+ {isLoading ? ( +
+ {[1, 2, 3].map((i) => ( + + ))} +
+ ) : ( +
+ {suggestedUsers.length > 0 ? ( +
+ {(suggestedUsers as { id: string; username?: string }[]).map((u) => ( +
+
+ {u.username ?? u.id} +
+ ))} +
+ ) : ( +

No suggestions for now.

+ )} +
+ )} + + + {isError && ( +

Could not load explore data.

+ )} +
+ ); +} diff --git a/apps/web/src/features/social/pages/social-page/SocialViewFeed.tsx b/apps/web/src/features/social/pages/social-page/SocialViewFeed.tsx index 6188f3684..8095fb7c2 100644 --- a/apps/web/src/features/social/pages/social-page/SocialViewFeed.tsx +++ b/apps/web/src/features/social/pages/social-page/SocialViewFeed.tsx @@ -24,9 +24,11 @@ interface SocialViewFeedProps { onLoadMore?: () => void; hasMore?: boolean; isLoadingMore?: boolean; + feedFilter?: 'all' | 'following' | 'groups'; + onFeedFilterChange?: (filter: 'all' | 'following' | 'groups') => void; } -export function SocialViewFeed({ items, loading, onPlayTrack, onLoadMore, hasMore, isLoadingMore }: SocialViewFeedProps) { +export function SocialViewFeed({ items, loading, onPlayTrack, onLoadMore, hasMore, isLoadingMore, feedFilter = 'all', onFeedFilterChange }: SocialViewFeedProps) { const [hasNewPosts, setHasNewPosts] = useState(false); // Simulate periodic new-posts availability @@ -46,7 +48,25 @@ export function SocialViewFeed({ items, loading, onPlayTrack, onLoadMore, hasMor

Community Feed

-

New uploads from the network

+

New uploads from the network

+ {onFeedFilterChange && ( +
+ {(['all', 'following', 'groups'] as const).map((f) => ( + + ))} +
+ )}
{/* New Posts Banner */} diff --git a/apps/web/src/features/social/pages/social-page/SocialViewSidebar.tsx b/apps/web/src/features/social/pages/social-page/SocialViewSidebar.tsx index e01dcab45..b67f1bb3b 100644 --- a/apps/web/src/features/social/pages/social-page/SocialViewSidebar.tsx +++ b/apps/web/src/features/social/pages/social-page/SocialViewSidebar.tsx @@ -1,6 +1,6 @@ import { Card } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { TrendingUp, Users } from 'lucide-react'; +import { Compass, TrendingUp, Users } from 'lucide-react'; import type { SocialTabKey } from './types'; interface SocialViewSidebarProps { @@ -48,7 +48,15 @@ export function SocialViewSidebar({ Fresh Tracks +