Witaj @name! Czas stworzyć Treasure Reels - platformę krótkich pirackich wideo! 🎬
To będzie jak TikTok - vertical short videos, For You algorithm, sounds, effects i duet/stitch features.
YouTube:
Instagram Reels:
TikTok:
1interface Video {
2 id: string;
3 authorId: string;
4 author: {
5 id: string;
6 username: string;
7 avatar: string;
8 isVerified: boolean;
9 };
10
11 // Video content
12 videoUrl: string;
13 thumbnail: string;
14 duration: number; // seconds
15
16 // Caption
17 caption: string;
18 hashtags: string[]; // #treasurehunt, #piratelife
19
20 // Audio
21 sound: Sound;
22
23 // Engagement
24 likes: number;
25 comments: number;
26 shares: number;
27 views: number;
28
29 // User engagement
30 hasLiked: boolean;
31 hasBookmarked: boolean;
32 isFollowingAuthor: boolean;
33
34 createdAt: Date;
35}
36
37interface Sound {
38 id: string;
39 name: string; // "Pirate's Anthem - Redbeard"
40 author: string;
41 duration: number;
42 useCount: number; // ile wideo używa tego sound
43 thumbnailUrl?: string;
44}1'use client';
2
3import { useState, useRef, useEffect } from 'react';
4
5interface FeedProps {
6 videos: Video[];
7 onLike: (videoId: string) => void;
8 onFollow: (authorId: string) => void;
9 onComment: (videoId: string) => void;
10 onShare: (videoId: string) => void;
11}
12
13export default function ForYouFeed({
14 videos,
15 onLike,
16 onFollow,
17 onComment,
18 onShare
19}: FeedProps) {
20 const [currentIndex, setCurrentIndex] = useState(0);
21 const [isPlaying, setIsPlaying] = useState(true);
22 const videoRefs = useRef<(HTMLVideoElement | null)[]>([]);
23
24 const currentVideo = videos[currentIndex];
25
26 // Auto-play current video
27 useEffect(() => {
28 const video = videoRefs.current[currentIndex];
29 if (video) {
30 if (isPlaying) {
31 video.play();
32 } else {
33 video.pause();
34 }
35 }
36 }, [currentIndex, isPlaying]);
37
38 const handleScroll = (direction: 'up' | 'down') => {
39 if (direction === 'down' && currentIndex < videos.length - 1) {
40 setCurrentIndex(currentIndex + 1);
41 } else if (direction === 'up' && currentIndex > 0) {
42 setCurrentIndex(currentIndex - 1);
43 }
44 };
45
46 const handleKeyDown = (e: React.KeyboardEvent) => {
47 if (e.key === 'ArrowUp') handleScroll('up');
48 if (e.key === 'ArrowDown') handleScroll('down');
49 if (e.key === ' ') {
50 e.preventDefault();
51 setIsPlaying(!isPlaying);
52 }
53 };
54
55 return (
56 <div
57 className="h-screen overflow-hidden bg-black relative"
58 onKeyDown={handleKeyDown}
59 tabIndex={0}
60 >
61 {/* Video Container */}
62 <div
63 className="absolute inset-0 transition-transform duration-300"
64 style={{
65 transform: `translateY(-${currentIndex * 100}%)`
66 }}
67 >
68 {videos.map((video, index) => (
69 <div
70 key={video.id}
71 className="h-screen w-full relative flex items-center justify-center"
72 >
73 {/* Video */}
74 <video
75 ref={el => videoRefs.current[index] = el}
76 src={video.videoUrl}
77 className="h-full w-auto max-w-full object-contain"
78 loop
79 playsInline
80 onClick={() => setIsPlaying(!isPlaying)}
81 />
82
83 {/* Play/Pause Indicator */}
84 {!isPlaying && index === currentIndex && (
85 <div className="absolute inset-0 flex items-center justify-center">
86 <div className="w-20 h-20 bg-black bg-opacity-50 rounded-full flex items-center justify-center">
87 <span className="text-white text-4xl">▶</span>
88 </div>
89 </div>
90 )}
91
92 {/* Video Info Overlay */}
93 {index === currentIndex && (
94 <VideoInfoOverlay
95 video={video}
96 onLike={() => onLike(video.id)}
97 onFollow={() => onFollow(video.author.id)}
98 onComment={() => onComment(video.id)}
99 onShare={() => onShare(video.id)}
100 />
101 )}
102 </div>
103 ))}
104 </div>
105
106 {/* Navigation Arrows */}
107 <div className="absolute right-4 top-1/2 transform -translate-y-1/2 flex flex-col gap-4">
108 <button
109 onClick={() => handleScroll('up')}
110 disabled={currentIndex === 0}
111 className="w-12 h-12 bg-white bg-opacity-20 hover:bg-opacity-30 disabled:bg-opacity-10 rounded-full flex items-center justify-center text-white"
112 >
113 ▲
114 </button>
115 <button
116 onClick={() => handleScroll('down')}
117 disabled={currentIndex === videos.length - 1}
118 className="w-12 h-12 bg-white bg-opacity-20 hover:bg-opacity-30 disabled:bg-opacity-10 rounded-full flex items-center justify-center text-white"
119 >
120 ▼
121 </button>
122 </div>
123
124 {/* Progress Indicator */}
125 <div className="absolute top-4 right-4 text-white text-sm bg-black bg-opacity-50 px-3 py-1 rounded-full">
126 {currentIndex + 1} / {videos.length}
127 </div>
128 </div>
129 );
130}1interface VideoInfoOverlayProps {
2 video: Video;
3 onLike: () => void;
4 onFollow: () => void;
5 onComment: () => void;
6 onShare: () => void;
7}
8
9function VideoInfoOverlay({
10 video,
11 onLike,
12 onFollow,
13 onComment,
14 onShare
15}: VideoInfoOverlayProps) {
16 return (
17 <>
18 {/* Right Side Actions */}
19 <div className="absolute right-4 bottom-20 flex flex-col gap-6 items-center">
20 {/* Author Avatar */}
21 <div className="relative">
22 <img
23 src={video.author.avatar}
24 alt={video.author.username}
25 className="w-12 h-12 rounded-full border-2 border-white"
26 />
27 {!video.isFollowingAuthor && (
28 <button
29 onClick={onFollow}
30 className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 w-6 h-6 bg-red-500 rounded-full text-white text-xl flex items-center justify-center"
31 >
32 +
33 </button>
34 )}
35 {video.author.isVerified && (
36 <div className="absolute -top-1 -right-1 w-4 h-4 bg-blue-500 rounded-full flex items-center justify-center text-white text-xs">
37 ✓
38 </div>
39 )}
40 </div>
41
42 {/* Like */}
43 <button onClick={onLike} className="flex flex-col items-center gap-1">
44 <div
45 className={`text-3xl transition ${
46 video.hasLiked ? 'text-red-500' : 'text-white'
47 }`}
48 >
49 {video.hasLiked ? '❤️' : '🤍'}
50 </div>
51 <span className="text-white text-xs font-semibold">
52 {formatCount(video.likes)}
53 </span>
54 </button>
55
56 {/* Comment */}
57 <button onClick={onComment} className="flex flex-col items-center gap-1">
58 <div className="text-3xl text-white">💬</div>
59 <span className="text-white text-xs font-semibold">
60 {formatCount(video.comments)}
61 </span>
62 </button>
63
64 {/* Bookmark */}
65 <button className="flex flex-col items-center gap-1">
66 <div
67 className={`text-3xl transition ${
68 video.hasBookmarked ? 'text-yellow-400' : 'text-white'
69 }`}
70 >
71 {video.hasBookmarked ? '🔖' : '📑'}
72 </div>
73 </button>
74
75 {/* Share */}
76 <button onClick={onShare} className="flex flex-col items-center gap-1">
77 <div className="text-3xl text-white">➤</div>
78 <span className="text-white text-xs font-semibold">
79 {formatCount(video.shares)}
80 </span>
81 </button>
82
83 {/* Sound */}
84 <button className="w-10 h-10 rounded-full overflow-hidden border-2 border-white animate-spin-slow">
85 <img
86 src={video.sound.thumbnailUrl || video.author.avatar}
87 alt={video.sound.name}
88 className="w-full h-full object-cover"
89 />
90 </button>
91 </div>
92
93 {/* Bottom Info */}
94 <div className="absolute bottom-4 left-4 right-20">
95 {/* Author */}
96 <div className="flex items-center gap-2 mb-2">
97 <span className="text-white font-bold">@{video.author.username}</span>
98 {video.author.isVerified && (
99 <span className="text-blue-400">✓</span>
100 )}
101 </div>
102
103 {/* Caption */}
104 <p className="text-white text-sm mb-2 line-clamp-2">
105 {video.caption}
106 </p>
107
108 {/* Hashtags */}
109 {video.hashtags.length > 0 && (
110 <div className="flex flex-wrap gap-2 mb-3">
111 {video.hashtags.map((tag, idx) => (
112 <span key={idx} className="text-white text-sm font-semibold">
113 #{tag}
114 </span>
115 ))}
116 </div>
117 )}
118
119 {/* Sound */}
120 <div className="flex items-center gap-2 bg-black bg-opacity-40 px-3 py-1.5 rounded-full max-w-xs">
121 <span className="text-white text-xs">🎵</span>
122 <span className="text-white text-xs truncate">
123 {video.sound.name}
124 </span>
125 </div>
126 </div>
127 </>
128 );
129}
130
131function formatCount(count: number): string {
132 if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
133 if (count >= 1000) return `${(count / 1000).toFixed(1)}K`;
134 return count.toString();
135}✅ Vertical video feed - fullscreen immersive scroll ✅ For You algorithm - personalized content ✅ Video overlay - author, caption, hashtags, sound ✅ Right side actions - like, comment, bookmark, share ✅ Scroll navigation - swipe up/down between videos ✅ Auto-play - video plays automatically when visible ✅ Play/pause - tap video to toggle ✅ Sound indicator - spinning disc with audio info ✅ Follow button - quick follow from avatar
W następnym ćwiczeniu dodamy sounds library i video creation!
Do zobaczenia! 🚀