We use cookies to enhance your experience on the site
CodeWorlds

Channel and Subscriptions

Twitch is about monetization - subscriptions, bits, followers.

Subscription System

1interface Subscription {
2  userId: string;
3  channelId: string;
4  tier: 'tier1' | 'tier2' | 'tier3'; // $4.99, $9.99, $24.99
5  startDate: Date;
6  renewDate: Date;
7  isGift: boolean;
8  giftedBy?: string;
9  streak: number; // consecutive months
10  cumulativeMonths: number; // total months
11}
12
13interface SubBenefits {
14  tier1: {
15    price: 4.99;
16    emotes: string[]; // unlock emotes
17    badge: string;
18    adFree: true;
19    chatPerks: {
20      slowModeExempt: true;
21      subsOnlyChat: true;
22    };
23  };
24  tier2: {
25    price: 9.99;
26    emotes: string[];
27    badge: string;
28    adFree: true;
29    chatPerks: {
30      slowModeExempt: true;
31      subsOnlyChat: true;
32    };
33  };
34  tier3: {
35    price: 24.99;
36    emotes: string[];
37    badge: string;
38    adFree: true;
39    chatPerks: {
40      slowModeExempt: true;
41      subsOnlyChat: true;
42    };
43  };
44}
45
46interface Bits {
47  userId: string;
48  channelId: string;
49  amount: number; // 100 bits = $1
50  message?: string;
51  timestamp: Date;
52}

Channel Page

1'use client';
2
3interface ChannelPageProps {
4  channel: Channel;
5  isFollowing: boolean;
6  isSubscribed: boolean;
7  subscriptionTier?: 'tier1' | 'tier2' | 'tier3';
8}
9
10export default function ChannelPage({
11  channel,
12  isFollowing,
13  isSubscribed,
14  subscriptionTier
15}: ChannelPageProps) {
16  const [activeTab, setActiveTab] = useState<'home' | 'videos' | 'schedule' | 'about'>('home');
17
18  return (
19    <div className="min-h-screen bg-gray-950 text-white">
20      {/* Banner */}
21      {channel.banner && (
22        <div
23          className="h-80 bg-cover bg-center"
24          style={{ backgroundImage: `url(${channel.banner})` }}
25        />
26      )}
27
28      {/* Channel Header */}
29      <div className="max-w-7xl mx-auto px-6 -mt-16 relative z-10">
30        <div className="flex items-end gap-6 mb-6">
31          {/* Avatar */}
32          <img
33            src={channel.avatar}
34            alt={channel.displayName}
35            className="w-32 h-32 rounded-full border-4 border-gray-950"
36          />
37
38          {/* Info */}
39          <div className="flex-1 pb-4">
40            <h1 className="text-4xl font-bold mb-2">{channel.displayName}</h1>
41            <p className="text-gray-400">{channel.description}</p>
42          </div>
43
44          {/* Actions */}
45          <div className="flex items-center gap-3 pb-4">
46            <button
47              className={`
48                px-6 py-2 rounded font-semibold
49                ${isFollowing
50                  ? 'bg-gray-700 hover:bg-gray-600'
51                  : 'bg-purple-600 hover:bg-purple-700'
52                }
53              `}
54            >
55              {isFollowing ? '❤️ Following' : '🤍 Follow'}
56            </button>
57
58            {!isSubscribed ? (
59              <button className="px-6 py-2 bg-purple-600 rounded font-semibold hover:bg-purple-700">
60Subscribe
61              </button>
62            ) : (
63              <button className="px-6 py-2 bg-purple-900 border border-purple-600 rounded font-semibold hover:bg-purple-800">
64Subscribed ({subscriptionTier})
65              </button>
66            )}
67
68            <button className="p-2 hover:bg-gray-800 rounded">
69              ⚙️
70            </button>
71          </div>
72        </div>
73
74        {/* Stats */}
75        <div className="flex gap-6 mb-6 text-sm text-gray-400">
76          <div>
77            <span className="font-semibold text-white">{channel.followers.toLocaleString()}</span> followers
78          </div>
79          <div>
80            <span className="font-semibold text-white">{channel.totalViews.toLocaleString()}</span> total views
81          </div>
82          {channel.isPartner && (
83            <div className="flex items-center gap-1">
84              <span className="text-purple-500"></span>
85              <span>Partner</span>
86            </div>
87          )}
88        </div>
89
90        {/* Tabs */}
91        <div className="border-b border-gray-800">
92          <div className="flex gap-8">
93            {[
94              { key: 'home', label: 'Home' },
95              { key: 'videos', label: 'Videos' },
96              { key: 'schedule', label: 'Schedule' },
97              { key: 'about', label: 'About' }
98            ].map(tab => (
99              <button
100                key={tab.key}
101                onClick={() => setActiveTab(tab.key as any)}
102                className={`
103                  pb-3 font-semibold
104                  ${activeTab === tab.key
105                    ? 'border-b-2 border-purple-500 text-white'
106                    : 'text-gray-400 hover:text-white'
107                  }
108                `}
109              >
110                {tab.label}
111              </button>
112            ))}
113          </div>
114        </div>
115      </div>
116
117      {/* Content */}
118      <div className="max-w-7xl mx-auto px-6 py-8">
119        {activeTab === 'home' && (
120          <div>
121            {/* Live Stream or Offline */}
122            {channel.isLive && channel.currentStream ? (
123              <div className="mb-8">
124                <h2 className="text-xl font-bold mb-4">Live Now</h2>
125                <StreamCard stream={channel.currentStream} />
126              </div>
127            ) : (
128              <div className="text-center py-12 text-gray-500">
129                <div className="text-4xl mb-4">📺</div>
130                <p className="text-lg">Channel is offline</p>
131                <p>Check back later or follow to get notified</p>
132              </div>
133            )}
134          </div>
135        )}
136
137        {activeTab === 'videos' && (
138          <div>
139            <h2 className="text-xl font-bold mb-4">Recent Videos</h2>
140            <div className="grid grid-cols-3 gap-4">
141              {/* VODs grid */}
142            </div>
143          </div>
144        )}
145
146        {activeTab === 'about' && (
147          <div className="max-w-2xl">
148            <h2 className="text-xl font-bold mb-4">About</h2>
149            <p className="text-gray-300 leading-relaxed">
150              {channel.description}
151            </p>
152          </div>
153        )}
154      </div>
155    </div>
156  );
157}
158
159function StreamCard({ stream }: { stream: Stream }) {
160  return (
161    <div className="bg-gray-900 rounded overflow-hidden hover:scale-105 transition cursor-pointer">
162      <div className="relative">
163        <img
164          src={stream.thumbnailUrl}
165          alt={stream.title}
166          className="w-full aspect-video object-cover"
167        />
168        <div className="absolute top-2 left-2 px-2 py-1 bg-red-600 text-white text-xs font-bold rounded">
169          LIVE
170        </div>
171        <div className="absolute bottom-2 left-2 px-2 py-1 bg-black bg-opacity-70 text-white text-xs rounded">
172          {stream.viewerCount.toLocaleString()} viewers
173        </div>
174      </div>
175
176      <div className="p-3">
177        <h3 className="font-semibold mb-1 line-clamp-1">{stream.title}</h3>
178        <p className="text-sm text-gray-400">{stream.category.name}</p>
179      </div>
180    </div>
181  );
182}

Summary 🎓

Channel page - banner, avatar, description, tabs ✅ Follow button - follow/unfollow channel ✅ Subscribe button - subscription tiers ✅ Channel stats - followers, total views ✅ Partner badge - verified partner status ✅ Live stream card - thumbnail, viewer count, category ✅ Offline state - message when not streaming ✅ Subscription tiers - tier1, tier2, tier3 with benefits

In the next exercise we'll add browse categories and clips!

See you soon! 🚀

Go to CodeWorlds