Improve ProfileSidebar by replacing avatar display with name and live badge; add Cloudflare Stream config schema; add API routes for fetching/updating Cloudflare config; update stream recordings storage path; enhance HLS check route with mock data for specific user.

This commit is contained in:
Nikita
2025-12-25 10:02:15 -08:00
parent c964660eda
commit 95f4270f9a
5 changed files with 93 additions and 17 deletions

View File

@@ -23,28 +23,17 @@ export function ProfileSidebar({ user, isLive, viewerCount, children }: ProfileS
<div className="h-full flex flex-col bg-black border-l border-white/10">
{/* Profile Header */}
<div className="p-4 border-b border-white/10">
{/* Avatar and Live Badge */}
{/* Name and Live Badge */}
<div className="flex items-start gap-3">
{user.image ? (
<div className="relative">
<img
src={user.image}
alt={displayName}
className="w-16 h-16 rounded-full bg-white/10"
/>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<h2 className="text-lg font-bold text-white truncate">{displayName}</h2>
{isLive && (
<span className="absolute -bottom-1 -right-1 px-1.5 py-0.5 text-[10px] font-bold uppercase bg-red-500 text-white rounded">
<span className="px-2 py-0.5 text-xs font-bold uppercase bg-red-500 text-white rounded">
Live
</span>
)}
</div>
) : isLive ? (
<div className="relative">
<div className="w-3 h-3 rounded-full bg-red-500 animate-pulse" />
</div>
) : null}
<div className="flex-1 min-w-0">
<h2 className="text-lg font-bold text-white truncate">{displayName}</h2>
<p className="text-sm text-white/60">@{user.username}</p>
</div>
</div>

View File

@@ -151,6 +151,21 @@ export type StreamRecording = z.infer<typeof StreamRecording>
*/
export const StreamRecordingList = co.list(StreamRecording)
/**
* Cloudflare Stream configuration
*/
export const CloudflareStreamConfig = co.map({
/** Cloudflare Live Input UID (permanent, doesn't change between connections) */
liveInputUid: z.string(),
/** Cloudflare customer code (e.g., xctsztqzu046isdc) */
customerCode: z.string(),
/** Stream name/title */
name: z.string(),
/** Last updated timestamp */
updatedAt: z.number(),
})
export type CloudflareStreamConfig = co.loaded<typeof CloudflareStreamConfig>
/**
* Viewer account root - stores any viewer-specific data
*/
@@ -163,6 +178,8 @@ export const ViewerRoot = co.map({
glideCanvas: GlideCanvasList,
/** Live stream recordings */
streamRecordings: StreamRecordingList,
/** Cloudflare Stream configuration */
cloudflareConfig: co.optional(CloudflareStreamConfig),
})
/**

View File

@@ -0,0 +1,42 @@
import { json } from "@tanstack/react-start"
import type { APIContext } from "@tanstack/react-router"
/**
* Get or set Cloudflare stream configuration from Jazz
*
* GET: Returns current Cloudflare Live Input UID
* PUT: Updates Cloudflare Live Input UID
*/
export async function GET({ request, context }: APIContext) {
try {
// For now, return the hardcoded value
// TODO: Read from Jazz when worker is set up
return json({
liveInputUid: "bb7858eafc85de6c92963f3817477b5d",
customerCode: "xctsztqzu046isdc",
name: "linsa-nikiv",
updatedAt: Date.now(),
})
} catch (error) {
return json({ error: "Failed to fetch config" }, { status: 500 })
}
}
export async function PUT({ request, context }: APIContext) {
try {
const body = await request.json()
const { liveInputUid, customerCode, name } = body
// TODO: Write to Jazz when worker is set up
// For now, just return success
return json({
success: true,
liveInputUid,
customerCode,
name,
updatedAt: Date.now(),
})
} catch (error) {
return json({ error: "Failed to update config" }, { status: 500 })
}
}

View File

@@ -6,7 +6,9 @@ import { promises as fs } from "fs"
* Chunks are stored temporarily and then synced to Jazz FileStream by client
*/
const STORAGE_PATH = "/Users/nikiv/fork-i/garden-co/jazz/glide-storage/stream-recordings"
const STORAGE_PATH =
process.env.STREAM_RECORDINGS_PATH ||
"/var/lib/jazz/stream-recordings"
interface StreamChunk {
streamId: string

View File

@@ -66,6 +66,32 @@ export const Route = createFileRoute("/api/streams/$username/check-hls")({
}
try {
// Hardcoded config for nikiv (stored in Jazz, not Postgres)
if (username === "nikiv") {
const hlsUrl = buildCloudflareHlsUrl("bb7858eafc85de6c92963f3817477b5d", "xctsztqzu046isdc")
const res = await fetch(hlsUrl, { cache: "no-store" })
if (!res.ok) {
return json({
isLive: false,
hlsUrl,
status: res.status,
error: "HLS not available",
})
}
const manifest = await res.text()
const isLive = isHlsPlaylistLive(manifest)
return json({
isLive,
hlsUrl,
status: res.status,
manifestLength: manifest.length,
})
}
const database = getDb(resolveDatabaseUrl())
const user = await database.query.users.findFirst({