We use cookies to enhance your experience on the site
CodeWorlds

Video Feed and For You Page

Welcome @name! Time to create Treasure Reels - a platform for short pirate videos! 🎬

This will be like TikTok - vertical short videos, For You algorithm, sounds, effects, and duet/stitch features.

TikTok vs YouTube/Instagram

YouTube:

  • Long-form horizontal video (10+ min)
  • Subscriptions and recommended
  • Comments under videos

Instagram Reels:

  • Short vertical video in Stories/Reels tab
  • Mainly photos + video as an addition
  • Grid layout

TikTok:

  • Pure short vertical video (15-60s)
  • For You Page algorithm (highly personalized)
  • Sounds library and audio trends
  • Duet, Stitch, Effects
  • Fullscreen immersive feed

Video Structure

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; // how many videos use this sound
43  thumbnailUrl?: string;
44}

For You Feed

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        >
113114        </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        >
120121        </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}

Video Info Overlay

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">
3738            </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}

Summary 🎓

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

In the next exercise we'll add sounds library and video creation!

See you soon! 🚀

Go to CodeWorlds