summaryrefslogtreecommitdiff
path: root/src/routes/channel
diff options
context:
space:
mode:
Diffstat (limited to 'src/routes/channel')
-rw-r--r--src/routes/channel/[id]/+page.svelte175
1 files changed, 175 insertions, 0 deletions
diff --git a/src/routes/channel/[id]/+page.svelte b/src/routes/channel/[id]/+page.svelte
new file mode 100644
index 0000000..05ba2d7
--- /dev/null
+++ b/src/routes/channel/[id]/+page.svelte
@@ -0,0 +1,175 @@
+<script lang="ts">
+ import { page } from '$app/stores';
+ import { getChannel, type ChannelInfo } from '$lib/api/youtube';
+ import { subscriptions } from '$lib/stores/subscriptions';
+ import VideoCard from '$lib/components/VideoCard.svelte';
+
+ let channel = $state<ChannelInfo | null>(null);
+ let loading = $state(true);
+ let error = $state('');
+
+ const channelId = $derived($page.params.id);
+ const isSubscribed = $derived(
+ channel ? subscriptions.isSubscribed($subscriptions, channel.authorId) : false
+ );
+
+ $effect(() => {
+ const id = $page.params.id;
+ if (id) loadChannel(id);
+ });
+
+ async function loadChannel(id: string) {
+ loading = true;
+ error = '';
+
+ try {
+ channel = await getChannel(id);
+ } catch (e) {
+ error = e instanceof Error ? e.message : 'Failed to load channel';
+ channel = null;
+ } finally {
+ loading = false;
+ }
+ }
+
+ function handleSubscribe() {
+ if (!channel) return;
+ if (isSubscribed) {
+ subscriptions.remove(channel.authorId);
+ } else {
+ subscriptions.add(channel.authorId, channel.author, channel.authorThumbnails);
+ }
+ }
+
+ function formatSubCount(count: number): string {
+ if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M subscribers`;
+ if (count >= 1000) return `${(count / 1000).toFixed(1)}K subscribers`;
+ return `${count} subscribers`;
+ }
+</script>
+
+<svelte:head>
+ <title>{channel?.author || 'Channel'} - ActualYT</title>
+</svelte:head>
+
+<div class="container">
+ {#if loading}
+ <div class="loading">Loading channel</div>
+ {:else if error}
+ <div class="error">{error}</div>
+ {:else if channel}
+ <div class="channel-page">
+ <div class="channel-header">
+ <div class="channel-info">
+ {#if channel.authorThumbnails && channel.authorThumbnails.length > 0}
+ <img
+ src={channel.authorThumbnails[0].url}
+ alt={channel.author}
+ class="avatar"
+ />
+ {/if}
+ <div class="channel-text">
+ <h1 class="channel-name">{channel.author}</h1>
+ {#if channel.subCount > 0}
+ <p class="sub-count">{formatSubCount(channel.subCount)}</p>
+ {/if}
+ </div>
+ </div>
+ <button
+ class="btn"
+ class:btn-primary={!isSubscribed}
+ class:btn-secondary={isSubscribed}
+ onclick={handleSubscribe}
+ >
+ {isSubscribed ? 'Subscribed' : 'Subscribe'}
+ </button>
+ </div>
+
+ {#if channel.description}
+ <p class="description">{channel.description}</p>
+ {/if}
+
+ <h2 class="section-title">Videos</h2>
+
+ {#if channel.videos.length === 0}
+ <div class="empty">No videos found</div>
+ {:else}
+ <div class="video-grid">
+ {#each channel.videos as video (video.videoId)}
+ <VideoCard
+ videoId={video.videoId}
+ title={video.title}
+ author={video.author}
+ authorId={video.authorId}
+ thumbnails={video.videoThumbnails}
+ viewCount={video.viewCount}
+ publishedText={video.publishedText}
+ lengthSeconds={video.lengthSeconds}
+ />
+ {/each}
+ </div>
+ {/if}
+ </div>
+ {/if}
+</div>
+
+<style>
+ .channel-page {
+ max-width: 1200px;
+ }
+
+ .channel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+ }
+
+ .channel-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ }
+
+ .avatar {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+
+ .channel-name {
+ font-size: 1.5rem;
+ font-weight: 600;
+ margin: 0 0 0.25rem;
+ }
+
+ .sub-count {
+ color: var(--text-muted);
+ margin: 0;
+ }
+
+ .description {
+ color: var(--text-secondary);
+ margin-bottom: 2rem;
+ white-space: pre-wrap;
+ line-height: 1.6;
+ }
+
+ @media (max-width: 600px) {
+ .channel-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .avatar {
+ width: 60px;
+ height: 60px;
+ }
+
+ .channel-name {
+ font-size: 1.25rem;
+ }
+ }
+</style>