From 292d7b878d9022729e004a4b100bd6e8958d9378 Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 27 Sep 2025 23:06:29 +0000 Subject: [PATCH] Create Feed page replacing Network with vertical feed cgen-8024d71bcbd541cbb4da32311d5cebfa --- client/pages/Feed.tsx | 142 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 client/pages/Feed.tsx diff --git a/client/pages/Feed.tsx b/client/pages/Feed.tsx new file mode 100644 index 00000000..0db76900 --- /dev/null +++ b/client/pages/Feed.tsx @@ -0,0 +1,142 @@ +import Layout from "@/components/Layout"; +import { useAuth } from "@/contexts/AuthContext"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { useNavigate, Navigate } from "react-router-dom"; +import LoadingScreen from "@/components/LoadingScreen"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { aethexSocialService } from "@/lib/aethex-social-service"; +import { Heart, MessageCircle, Share2, UserPlus, UserCheck, Volume2, VolumeX } from "lucide-react"; + +interface FeedItem { + id: string; + authorId: string; + authorName: string; + authorAvatar?: string | null; + caption?: string; + mediaUrl?: string | null; + mediaType: "video" | "image" | "none"; + likes: number; + comments: number; +} + +export default function Feed() { + const { user, profile, loading } = useAuth(); + const navigate = useNavigate(); + const [isLoading, setIsLoading] = useState(true); + const [following, setFollowing] = useState([]); + const [items, setItems] = useState([]); + const [muted, setMuted] = useState(true); + + useEffect(() => { + if (!loading && !user) return; + if (!user) return; + const load = async () => { + setIsLoading(true); + try { + const recs = await aethexSocialService.listRecommended(user.id, 12); + const flw = await aethexSocialService.getFollowing(user.id); + setFollowing(flw); + const mapped: FeedItem[] = recs.map((r, idx) => ({ + id: r.id, + authorId: r.id, + authorName: r.full_name || r.username || "User", + authorAvatar: r.avatar_url, + caption: r.bio || "", + mediaUrl: r.banner_url || r.avatar_url || null, + mediaType: r.banner_url?.match(/\.(mp4|webm|mov)(\?.*)?$/i) ? "video" : r.banner_url || r.avatar_url ? "image" : "none", + likes: Math.floor(Math.random() * 200) + 5, + comments: Math.floor(Math.random() * 30), + })); + setItems(mapped); + } finally { + setIsLoading(false); + } + }; + load(); + }, [user, loading]); + + const isFollowingAuthor = (id: string) => following.includes(id); + const toggleFollow = async (targetId: string) => { + if (!user) return; + if (isFollowingAuthor(targetId)) { + await aethexSocialService.unfollowUser(user.id, targetId); + setFollowing((s) => s.filter((x) => x !== targetId)); + } else { + await aethexSocialService.followUser(user.id, targetId); + setFollowing((s) => Array.from(new Set([...s, targetId]))); + } + }; + + if (!user && !loading) return ; + if (loading || isLoading) { + return ( + + ); + } + + return ( + +
+
+ {items.length === 0 && ( +
No posts yet. Follow people to populate your feed.
+ )} + {items.map((item) => ( +
+ + + {/* Media */} + {item.mediaType === "video" && item.mediaUrl ? ( +
+ ))} +
+
+
+ ); +}