YouTube lets you organize videos into playlists. Pirates will group their adventures into collections!
1interface Playlist {
2 id: string;
3 title: string;
4 description: string;
5 thumbnail: string; // Thumbnail of the first video
6 creator: {
7 id: string;
8 name: string;
9 avatar: string;
10 };
11 videos: Video[];
12 isPublic: boolean;
13 createdDate: Date;
14 updatedDate: Date;
15}1function PlaylistCard({ playlist }: { playlist: Playlist }) {
2 return (
3 <div className="group cursor-pointer">
4 {/* Playlist Thumbnail Stack */}
5 <div className="relative aspect-video bg-gray-900 rounded-lg overflow-hidden mb-2">
6 {/* Main Thumbnail */}
7 <img
8 src={playlist.thumbnail}
9 alt={playlist.title}
10 className="w-full h-full object-cover"
11 />
12
13 {/* Playlist Overlay */}
14 <div className="absolute inset-0 bg-gradient-to-r from-transparent to-black/80 flex items-center justify-end p-4">
15 <div className="text-white text-right">
16 <div className="text-4xl mb-2">📋</div>
17 <p className="font-bold">{playlist.videos.length}</p>
18 <p className="text-xs">videos</p>
19 </div>
20 </div>
21
22 {/* Play All Button */}
23 <div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-50 transition-opacity flex items-center justify-center">
24 <button className="opacity-0 group-hover:opacity-100 transition-opacity bg-white text-black px-6 py-3 rounded-full font-bold flex items-center gap-2">
25 ▶ Play all
26 </button>
27 </div>
28 </div>
29
30 {/* Playlist Info */}
31 <h3 className="font-semibold line-clamp-2 mb-1">{playlist.title}</h3>
32 <p className="text-sm text-gray-600">
33 {playlist.creator.name}
34 </p>
35 <p className="text-sm text-gray-600">
36 {playlist.isPublic ? 'Public' : 'Private'} • {playlist.videos.length} videos
37 </p>
38 </div>
39 );
40}1'use client';
2
3import { useState } from 'react';
4
5export default function PlaylistPage({ playlist }: { playlist: Playlist }) {
6 const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
7 const currentVideo = playlist.videos[currentVideoIndex];
8
9 return (
10 <div className="max-w-7xl mx-auto p-4">
11 <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
12 {/* Left - Video Player */}
13 <div className="lg:col-span-2">
14 <VideoPlayer video={currentVideo} />
15
16 <div className="mt-4 bg-gradient-to-r from-purple-600 to-blue-600 text-white p-4 rounded-lg">
17 <div className="flex items-center gap-3 mb-2">
18 <span className="text-2xl">📋</span>
19 <div>
20 <h2 className="font-bold text-lg">{playlist.title}</h2>
21 <p className="text-sm opacity-90">
22 {currentVideoIndex + 1} / {playlist.videos.length}
23 </p>
24 </div>
25 </div>
26 <p className="text-sm opacity-80">{playlist.description}</p>
27 </div>
28 </div>
29
30 {/* Right - Playlist Videos */}
31 <div className="bg-white rounded-lg shadow">
32 <div className="p-4 border-b">
33 <h3 className="font-bold">Playlist</h3>
34 <p className="text-sm text-gray-600">
35 {playlist.videos.length} videos
36 </p>
37 </div>
38
39 <div className="max-h-[600px] overflow-y-auto">
40 {playlist.videos.map((video, index) => (
41 <div
42 key={video.id}
43 onClick={() => setCurrentVideoIndex(index)}
44 className={`flex gap-3 p-3 cursor-pointer border-b hover:bg-gray-50 ${
45 index === currentVideoIndex ? 'bg-blue-50' : ''
46 }`}
47 >
48 {/* Index */}
49 <div className="flex-shrink-0 w-6 text-center text-gray-600 font-medium">
50 {index === currentVideoIndex ? '▶' : index + 1}
51 </div>
52
53 {/* Thumbnail */}
54 <div className="relative w-32 aspect-video bg-gray-200 rounded flex-shrink-0">
55 <img
56 src={video.thumbnail}
57 alt={video.title}
58 className="w-full h-full object-cover"
59 />
60 <div className="absolute bottom-1 right-1 bg-black bg-opacity-80 text-white text-xs px-1 rounded">
61 {formatTime(video.duration)}
62 </div>
63 </div>
64
65 {/* Info */}
66 <div className="flex-1 min-w-0">
67 <h4 className="text-sm font-semibold line-clamp-2 mb-1">
68 {video.title}
69 </h4>
70 <p className="text-xs text-gray-600">
71 {video.channel.name}
72 </p>
73 </div>
74 </div>
75 ))}
76 </div>
77 </div>
78 </div>
79 </div>
80 );
81}1'use client';
2
3import { useState } from 'react';
4
5export default function CreatePlaylist({ onSave }: { onSave: (playlist: Playlist) => void }) {
6 const [title, setTitle] = useState('');
7 const [description, setDescription] = useState('');
8 const [isPublic, setIsPublic] = useState(true);
9
10 const handleSubmit = (e: React.FormEvent) => {
11 e.preventDefault();
12
13 const newPlaylist: Playlist = {
14 id: Date.now().toString(),
15 title,
16 description,
17 thumbnail: '/default-playlist.jpg',
18 creator: {
19 id: 'current-user',
20 name: 'Captain Redbeard',
21 avatar: '/avatar.jpg',
22 },
23 videos: [],
24 isPublic,
25 createdDate: new Date(),
26 updatedDate: new Date(),
27 };
28
29 onSave(newPlaylist);
30 };
31
32 return (
33 <div className="bg-white rounded-lg shadow p-6">
34 <h2 className="text-xl font-bold mb-4">Create New Playlist</h2>
35
36 <form onSubmit={handleSubmit} className="space-y-4">
37 <div>
38 <label className="block font-medium mb-2">Title</label>
39 <input
40 type="text"
41 value={title}
42 onChange={(e) => setTitle(e.target.value)}
43 className="w-full p-3 border rounded-lg"
44 required
45 />
46 </div>
47
48 <div>
49 <label className="block font-medium mb-2">Description</label>
50 <textarea
51 value={description}
52 onChange={(e) => setDescription(e.target.value)}
53 className="w-full p-3 border rounded-lg resize-none"
54 rows={4}
55 />
56 </div>
57
58 <div>
59 <label className="flex items-center gap-2 cursor-pointer">
60 <input
61 type="checkbox"
62 checked={isPublic}
63 onChange={(e) => setIsPublic(e.target.checked)}
64 className="w-4 h-4"
65 />
66 <span className="font-medium">Public</span>
67 </label>
68 <p className="text-sm text-gray-600 ml-6">
69 {isPublic
70 ? 'Anyone can view this playlist'
71 : 'Only you can view this playlist'}
72 </p>
73 </div>
74
75 <button
76 type="submit"
77 className="w-full py-3 bg-red-600 text-white rounded-lg font-bold hover:bg-red-700 transition"
78 >
79 Create Playlist
80 </button>
81 </form>
82 </div>
83 );
84}1'use client';
2
3import { useState } from 'react';
4
5interface AddToPlaylistProps {
6 video: Video;
7 playlists: Playlist[];
8 onAdd: (playlistId: string, video: Video) => void;
9}
10
11export default function AddToPlaylist({ video, playlists, onAdd }: AddToPlaylistProps) {
12 const [isOpen, setIsOpen] = useState(false);
13
14 return (
15 <>
16 <button
17 onClick={() => setIsOpen(true)}
18 className="flex items-center gap-2 px-4 py-2 bg-gray-100 rounded-full hover:bg-gray-200"
19 >
20 📋 Save
21 </button>
22
23 {isOpen && (
24 <div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
25 <div className="bg-white rounded-lg max-w-md w-full">
26 <div className="flex items-center justify-between p-4 border-b">
27 <h3 className="font-bold">Save to playlist</h3>
28 <button
29 onClick={() => setIsOpen(false)}
30 className="text-gray-500 hover:text-gray-700 text-2xl"
31 >
32 ✕
33 </button>
34 </div>
35
36 <div className="p-4 max-h-96 overflow-y-auto">
37 {playlists.length === 0 ? (
38 <p className="text-gray-600 text-center py-8">
39 No playlists yet. Create one!
40 </p>
41 ) : (
42 <div className="space-y-2">
43 {playlists.map(playlist => {
44 const isInPlaylist = playlist.videos.some(v => v.id === video.id);
45
46 return (
47 <label
48 key={playlist.id}
49 className="flex items-center gap-3 p-3 hover:bg-gray-50 rounded cursor-pointer"
50 >
51 <input
52 type="checkbox"
53 checked={isInPlaylist}
54 onChange={() => {
55 if (!isInPlaylist) {
56 onAdd(playlist.id, video);
57 }
58 }}
59 className="w-4 h-4"
60 />
61 <div className="flex-1">
62 <h4 className="font-medium">{playlist.title}</h4>
63 <p className="text-sm text-gray-600">
64 {playlist.videos.length} videos • {playlist.isPublic ? 'Public' : 'Private'}
65 </p>
66 </div>
67 </label>
68 );
69 })}
70 </div>
71 )}
72 </div>
73
74 <div className="p-4 border-t">
75 <button className="w-full py-2 bg-blue-600 text-white rounded-lg font-bold hover:bg-blue-700">
76 + Create new playlist
77 </button>
78 </div>
79 </div>
80 </div>
81 )}
82 </>
83 );
84}✅ Playlist structure - title, description, videos array ✅ Playlist card - thumbnail stack, video count ✅ Playlist page - player + video list ✅ Current video tracking - index state ✅ Create playlist - form with public/private toggle ✅ Add to playlist - modal with checkboxes ✅ Auto-play next - sequential playback ✅ Playlist management - add/remove videos
In the next exercise we'll build Channel pages and subscriptions!
See you there! 🚀