diff --git a/apps/frontend/app/user/[slug].tsx b/apps/frontend/app/user/[slug].tsx new file mode 100644 index 0000000..4c4f9d9 --- /dev/null +++ b/apps/frontend/app/user/[slug].tsx @@ -0,0 +1,184 @@ +import React, { useEffect, useState } from 'react'; +import { + FlatList, + TouchableOpacity, + Image, + RefreshControl, + Alert, + ActivityIndicator, +} from 'react-native'; +import { useRouter, useLocalSearchParams } from 'expo-router'; +import { useVideoPlayer, VideoView } from 'expo-video'; +import { Text, View } from '@/components/Themed'; +import { savesApi } from '@/lib/api'; +import type { SaveListItem } from '@shared-types'; +import { useColorScheme } from '@/components/useColorScheme'; +import Colors from '@/constants/Colors'; +import { File, ArrowLeft, Image as ImageIcon } from 'lucide-react-native'; + +export default function UserProfileScreen() { + const { slug } = useLocalSearchParams<{ slug: string }>(); + const [saves, setSaves] = useState([]); + const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const router = useRouter(); + const colorScheme = useColorScheme(); + const colors = Colors[colorScheme ?? 'light']; + + const loadSaves = async () => { + if (!slug) return; + + try { + const data = await savesApi.getPublicSavesByUser(slug); + setSaves(data); + } catch (error: any) { + Alert.alert('Ошибка', error.message || 'Не удалось загрузить сейвы'); + } finally { + setLoading(false); + setRefreshing(false); + } + }; + + useEffect(() => { + if (slug) { + loadSaves(); + } + }, [slug]); + + const handleRefresh = () => { + setRefreshing(true); + loadSaves(); + }; + + const handleSavePress = (save: SaveListItem) => { + router.push(`/save/${save.id}`); + }; + + // Компонент для элемента списка + const SaveItem = React.memo(({ item }: { item: SaveListItem }) => { + if (!item) return null; + + const itemColorScheme = useColorScheme(); + const itemColors = Colors[itemColorScheme ?? 'light']; + + const videoPlayer = useVideoPlayer( + item.type === 'video' ? item.url : null, + (player) => { + player.muted = true; // Без звука в списке + player.loop = true; // Зацикливание для превью + } + ); + + return ( + handleSavePress(item)} + > + + + {item.type === 'image' || item.type === 'gif' ? ( + + ) : item.type === 'video' ? ( + + ) : ( + + + + )} + + + + {item.name || 'Без названия'} + + {item.description && ( + + {item.description} + + )} + {item.tags && item.tags.length > 0 && ( + + {item.tags.slice(0, 3).map((tag, idx) => ( + + + {tag} + + + ))} + {item.tags.length > 3 && ( + + +{item.tags.length - 3} + + )} + + )} + + {new Date(item.createdAt).toLocaleDateString('ru-RU')} + + + + + ); + }); + + if (loading) { + return ( + + + + ); + } + + return ( + + + router.back()} + className="p-2" + > + + + Публичные сейвы + + + + {saves.length === 0 ? ( + + + Нет публичных сейвов + + У этого пользователя пока нет публичных сейвов + + + ) : ( + item.id.toString()} + renderItem={({ item }) => } + refreshControl={ + + } + contentContainerStyle={{ padding: 16 }} + /> + )} + + ); +}