// components/social/EnhancedSocialPost.tsx import React, { useEffect, useState, useMemo } from 'react'; import { View, TouchableOpacity, Image, ScrollView } from 'react-native'; import { Text } from '@/components/ui/text'; import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'; import { Badge } from '@/components/ui/badge'; import { Heart, MessageCircle, Repeat, Share, User, Clock, Dumbbell, CheckCircle, FileText } from 'lucide-react-native'; import { useProfile } from '@/lib/hooks/useProfile'; import { useNDK } from '@/lib/hooks/useNDK'; import { FeedItem } from '@/lib/hooks/useSocialFeed'; import { SocialFeedService } from '@/lib/social/socialFeedService'; import { ParsedSocialPost, ParsedWorkoutRecord, ParsedExerciseTemplate, ParsedWorkoutTemplate, ParsedLongformContent } from '@/types/nostr-workout'; import { formatDistance } from 'date-fns'; import Markdown from 'react-native-markdown-display'; // Helper functions for all components to use // Format timestamp function formatTimestamp(timestamp: number) { try { return formatDistance(new Date(timestamp * 1000), new Date(), { addSuffix: true }); } catch (error) { return 'recently'; } } // Helper function to format duration in ms to readable format function formatDuration(milliseconds: number): string { const minutes = Math.floor(milliseconds / 60000); if (minutes < 60) { return `${minutes}m`; } const hours = Math.floor(minutes / 60); const mins = minutes % 60; if (mins === 0) { return `${hours}h`; } return `${hours}h ${mins}m`; } // Helper function to format minutes function formatMinutes(minutes: number): string { if (isNaN(minutes)) return ''; if (minutes < 60) { return `${Math.floor(minutes)}m`; } const hours = Math.floor(minutes / 60); const mins = Math.floor(minutes % 60); if (mins === 0) { return `${hours}h`; } return `${hours}h ${mins}m`; } interface SocialPostProps { item: FeedItem; onPress?: () => void; } export default function EnhancedSocialPost({ item, onPress }: SocialPostProps) { const { ndk } = useNDK(); const [liked, setLiked] = useState(false); const [likeCount, setLikeCount] = useState(0); const [imageError, setImageError] = useState(false); const { profile } = useProfile(item.originalEvent.pubkey); // Get likes count useEffect(() => { if (!ndk) return; let mounted = true; const fetchLikes = async () => { try { const filter = { kinds: [7], // Reactions '#e': [item.id] }; const events = await ndk.fetchEvents(filter); if (mounted) { setLikeCount(events.size); } } catch (error) { console.error('Error fetching likes:', error); } }; fetchLikes(); return () => { mounted = false; }; }, [ndk, item.id]); // Handle like button press const handleLike = async () => { if (!ndk) return; try { const socialService = new SocialFeedService(ndk); await socialService.reactToEvent(item.originalEvent); setLiked(true); setLikeCount(prev => prev + 1); } catch (error) { console.error('Error liking post:', error); } }; // Handle image error const handleImageError = () => { setImageError(true); }; // Render based on feed item type const renderContent = () => { switch (item.type) { case 'workout': return ; case 'exercise': return ; case 'template': return ; case 'social': return ; case 'article': return ; default: return null; } }; // Memoize the author name to prevent unnecessary re-renders const authorName = useMemo(() => { return profile?.name || 'Nostr User'; }, [profile?.name]); return ( {profile?.image && !imageError ? ( ) : ( )} {authorName} {profile?.nip05 && ( )} {formatTimestamp(item.createdAt)} {renderContent()} {/* Reduced space between content and action buttons */} {likeCount > 0 && ( {likeCount} )} {/* Hairline divider */} ); } // Component for workout records function WorkoutContent({ workout }: { workout: ParsedWorkoutRecord }) { return ( {workout.title} {workout.notes && ( {workout.notes} )} {workout.type} workout {workout.startTime && workout.endTime && ( {formatDuration(workout.endTime - workout.startTime)} )} {workout.exercises.length > 0 && ( Exercises: {workout.exercises.slice(0, 3).map((exercise, index) => ( • {exercise.name} {exercise.weight ? ` - ${exercise.weight}kg` : ''} {exercise.reps ? ` × ${exercise.reps}` : ''} {exercise.rpe ? ` @ RPE ${exercise.rpe}` : ''} ))} {workout.exercises.length > 3 && ( +{workout.exercises.length - 3} more exercises )} )} ); } // Component for exercise templates function ExerciseContent({ exercise }: { exercise: ParsedExerciseTemplate }) { return ( {exercise.title} {exercise.description && ( {exercise.description} )} {exercise.equipment && ( {exercise.equipment} )} {exercise.difficulty && ( {exercise.difficulty} )} {exercise.format.length > 0 && ( Tracks: {exercise.format.map((format, index) => ( {format} ))} )} {exercise.tags.length > 0 && ( {exercise.tags.map((tag, index) => ( #{tag} ))} )} ); } // Component for workout templates function TemplateContent({ template }: { template: ParsedWorkoutTemplate }) { return ( {template.title} {template.description && ( {template.description} )} {template.type} template {template.duration && ( {formatMinutes(template.duration / 60)} {/* Convert seconds to minutes */} )} {template.rounds && ( {template.rounds} {template.rounds === 1 ? 'round' : 'rounds'} )} {template.exercises.length > 0 && ( Exercises: {template.exercises.slice(0, 3).map((exercise, index) => ( • {exercise.name || 'Exercise ' + (index + 1)} ))} {template.exercises.length > 3 && ( +{template.exercises.length - 3} more exercises )} )} {template.tags.length > 0 && ( {template.tags.map((tag, index) => ( #{tag} ))} )} ); } // Component for social posts function SocialContent({ post }: { post: ParsedSocialPost }) { // Render the social post content const renderMainContent = () => ( {post.content} ); // Render quoted content if available const renderQuotedContent = () => { if (!post.quotedContent || !post.quotedContent.resolved) return null; const { type, resolved } = post.quotedContent; return ( {type === 'workout' ? 'Workout' : type === 'exercise' ? 'Exercise' : type === 'template' ? 'Workout Template' : type === 'article' ? 'Article' : 'Post'}: {type === 'workout' && } {type === 'exercise' && } {type === 'template' && } {type === 'article' && } ); }; return ( {renderMainContent()} {renderQuotedContent()} ); } // Component for long-form content function ArticleContent({ article }: { article: ParsedLongformContent }) { // Limit content preview to a reasonable length const previewLength = 200; const hasFullContent = article.content && article.content.length > previewLength; return ( Article {article.title} {article.image && ( )} {article.summary ? ( {article.summary} ) : ( {hasFullContent ? article.content.substring(0, previewLength) + '...' : article.content} )} {article.publishedAt && ( Published: {formatTimestamp(article.publishedAt)} )} {article.tags.length > 0 && ( {article.tags.map((tag, index) => ( #{tag} ))} )} ); } // Add ArticleQuote component for quoted articles function ArticleQuote({ article }: { article: ParsedLongformContent }) { return ( {article.title} {article.summary ? ( {article.summary} ) : ( {article.content ? article.content.substring(0, 100) + '...' : 'No content'} )} ); } // Simplified versions of content for quoted posts function WorkoutQuote({ workout }: { workout: ParsedWorkoutRecord }) { return ( {workout.title} {workout.exercises.length} exercises • { workout.startTime && workout.endTime ? formatDuration(workout.endTime - workout.startTime) : 'Duration N/A' } ); } function ExerciseQuote({ exercise }: { exercise: ParsedExerciseTemplate }) { return ( {exercise.title} {exercise.equipment && ( {exercise.equipment} • {exercise.difficulty || 'Any level'} )} ); } function TemplateQuote({ template }: { template: ParsedWorkoutTemplate }) { return ( {template.title} {template.type} • {template.exercises.length} exercises ); }