// 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 { Badge } from '@/components/ui/badge';
import { Heart, MessageCircle, Repeat, Share, Clock, Dumbbell, CheckCircle, FileText, User } from 'lucide-react-native';
import UserAvatar from '@/components/UserAvatar';
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 { 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);
}
};
// 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':
// Only show ArticleContent for published articles (kind 30023)
// Never show draft articles (kind 30024)
if (item.originalEvent.kind === 30023) {
return ;
} else {
// For any other kinds, render as a social post
// Create a proper ParsedSocialPost object with all required fields
return ;
}
default:
return null;
}
};
// Memoize the author name to prevent unnecessary re-renders
const authorName = useMemo(() => {
return profile?.name || 'Nostr User';
}, [profile?.name]);
return (
{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
);
}