From 3133bd2b5c4265b22c4202e04886b904c1a5cabb Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 27 Sep 2025 23:33:29 +0000 Subject: [PATCH] Integrate PostComposer and community posts into Feed cgen-c813287b31b3444fb531449411433b73 --- client/pages/Feed.tsx | 194 +++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 98 deletions(-) diff --git a/client/pages/Feed.tsx b/client/pages/Feed.tsx index b597071d..5310dcd6 100644 --- a/client/pages/Feed.tsx +++ b/client/pages/Feed.tsx @@ -1,12 +1,16 @@ import Layout from "@/components/Layout"; +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 { useEffect, useState } from "react"; +import { 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 { communityService, realtimeService } from "@/lib/supabase-service"; +import PostComposer from "@/components/social/PostComposer"; +import { useToast } from "@/hooks/use-toast"; import { Heart, MessageCircle, @@ -29,44 +33,82 @@ interface FeedItem { comments: number; } +function parseContent(content: string): { text?: string; mediaUrl?: string | null; mediaType: "video" | "image" | "none" } { + try { + const obj = JSON.parse(content || "{}"); + return { + text: obj.text || content, + mediaUrl: obj.mediaUrl || null, + mediaType: obj.mediaType || (obj.mediaUrl ? (/(mp4|webm|mov)$/i.test(obj.mediaUrl) ? "video" : "image") : "none"), + }; + } catch { + return { text: content, mediaUrl: null, mediaType: "none" }; + } +} + export default function Feed() { - const { user, profile, loading } = useAuth(); - const navigate = useNavigate(); + const { user, loading } = useAuth(); + const { toast } = useToast(); 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 posts = await communityService.getPosts(20); 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); + const mapped: FeedItem[] = posts.map((p: any) => { + const meta = parseContent(p.content); + const author = p.user_profiles || {}; + return { + id: p.id, + authorId: p.author_id, + authorName: author.full_name || author.username || "User", + authorAvatar: author.avatar_url, + caption: meta.text, + mediaUrl: meta.mediaUrl, + mediaType: meta.mediaType, + likes: p.likes_count ?? 0, + comments: p.comments_count ?? 0, + }; + }); + // If no posts yet, fall back to recommended people as placeholders + if (mapped.length === 0) { + const recs = await aethexSocialService.listRecommended(user.id, 12); + const placeholders: FeedItem[] = recs.map((r: any) => ({ + 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(placeholders); + } else { + setItems(mapped); + } } finally { setIsLoading(false); } }; load(); + + const sub = realtimeService.subscribeToCommunityPosts(() => load()); + return () => { + try { sub.unsubscribe(); } catch {} + }; }, [user, loading]); const isFollowingAuthor = (id: string) => following.includes(id); @@ -81,128 +123,84 @@ export default function Feed() { } }; + const share = async (id: string) => { + const url = `${location.origin}/feed#post-${id}`; + try { + if ((navigator as any).share) { + await (navigator as any).share({ title: "AeThex", text: "Check this post", url }); + } else { + await navigator.clipboard.writeText(url); + toast({ description: "Link copied" }); + } + } catch {} + }; + if (!user && !loading) return ; if (loading || isLoading) { return ( - + ); } return (
-
+
+ setIsLoading(true)} /> +
+
{items.length === 0 && (
- No posts yet. Follow people to populate your feed. + No posts yet. Share something to start the feed.
)} {items.map((item) => ( -
+
- {/* Media */} {item.mediaType === "video" && item.mediaUrl ? ( -