diff --git a/packages/web/src/components/ProfileSidebar.tsx b/packages/web/src/components/ProfileSidebar.tsx
new file mode 100644
index 00000000..3ff7e2ec
--- /dev/null
+++ b/packages/web/src/components/ProfileSidebar.tsx
@@ -0,0 +1,97 @@
+import { Link } from "@tanstack/react-router"
+import { ExternalLink, MapPin, Calendar, Users } from "lucide-react"
+
+interface ProfileSidebarProps {
+ user: {
+ id: string
+ name: string | null
+ username: string
+ image: string | null
+ bio?: string | null
+ website?: string | null
+ location?: string | null
+ joinedAt?: string | null
+ }
+ isLive?: boolean
+ viewerCount?: number
+ children?: React.ReactNode
+}
+
+export function ProfileSidebar({ user, isLive, viewerCount, children }: ProfileSidebarProps) {
+ const displayName = user.name || user.username
+ const avatarUrl = user.image || `https://api.dicebear.com/7.x/initials/svg?seed=${user.username}`
+
+ return (
+
+ {/* Profile Header */}
+
+ {/* Avatar and Live Badge */}
+
+
+

+ {isLive && (
+
+ Live
+
+ )}
+
+
+
{displayName}
+
@{user.username}
+
+
+
+ {/* Bio */}
+ {user.bio && (
+
{user.bio}
+ )}
+
+ {/* Meta info */}
+
+ {user.location && (
+
+
+ {user.location}
+
+ )}
+ {user.website && (
+
+
+ {user.website.replace(/^https?:\/\//, "")}
+
+ )}
+ {user.joinedAt && (
+
+
+ Joined {new Date(user.joinedAt).toLocaleDateString("en-US", { month: "short", year: "numeric" })}
+
+ )}
+
+
+ {/* Stats */}
+ {isLive && viewerCount !== undefined && (
+
+
+
+ {viewerCount} watching
+
+
+ )}
+
+
+ {/* Children (Chat, etc.) */}
+
+ {children}
+
+
+ )
+}
diff --git a/packages/web/src/routes/$username.tsx b/packages/web/src/routes/$username.tsx
index 5d100cf1..110df05c 100644
--- a/packages/web/src/routes/$username.tsx
+++ b/packages/web/src/routes/$username.tsx
@@ -2,20 +2,16 @@ import { useEffect, useRef, useState } from "react"
import { createFileRoute, Link } from "@tanstack/react-router"
import { getStreamByUsername, type StreamPageData } from "@/lib/stream/db"
import { VideoPlayer } from "@/components/VideoPlayer"
-import { CloudflareStreamPlayer } from "@/components/CloudflareStreamPlayer"
-import { WebRTCPlayer } from "@/components/WebRTCPlayer"
-import { LiveNowSidebar } from "@/components/LiveNowSidebar"
import { resolveStreamPlayback } from "@/lib/stream/playback"
import { JazzProvider } from "@/lib/jazz/provider"
-import { ViewerCount } from "@/components/ViewerCount"
import { CommentBox } from "@/components/CommentBox"
+import { ProfileSidebar } from "@/components/ProfileSidebar"
import {
getSpotifyNowPlaying,
type SpotifyNowPlayingResponse,
} from "@/lib/spotify/now-playing"
-import { getStreamStatus } from "@/lib/stream/status"
import { authClient } from "@/lib/auth-client"
-import { MessageCircle, LogIn, X } from "lucide-react"
+import { MessageCircle, LogIn, X, User } from "lucide-react"
export const Route = createFileRoute("/$username")({
ssr: false,
@@ -29,9 +25,13 @@ function makeNikivData(hlsUrl: string): StreamPageData {
return {
user: {
id: "nikiv",
- name: "Nikita",
+ name: "Nikita Voloboev",
username: "nikiv",
- image: null,
+ image: "https://nikiv.dev/nikiv.jpg",
+ bio: "Building in public. Making tools I want to exist.",
+ website: "nikiv.dev",
+ location: null,
+ joinedAt: "2024-01-01",
},
stream: {
id: "nikiv-stream",
@@ -77,12 +77,8 @@ function StreamPage() {
let isActive = true
// Special handling for nikiv - URL comes from API (secret)
+ // Data and loading state are handled by the HLS check effect
if (username === "nikiv") {
- // Data will be set when we get the HLS URL from the API
- if (hlsUrl) {
- setData(makeNikivData(hlsUrl))
- }
- setLoading(false)
return () => {
isActive = false
}
@@ -190,6 +186,7 @@ function StreamPage() {
if (username !== "nikiv") return
let isActive = true
+ let isFirstCheck = true
const checkHlsViaApi = async () => {
try {
@@ -219,6 +216,12 @@ function StreamPage() {
}
} catch {
// Silently ignore errors - don't change state on network issues
+ } finally {
+ // Mark loading as done after first check completes
+ if (isActive && isFirstCheck) {
+ isFirstCheck = false
+ setLoading(false)
+ }
}
}