import { Head, Link, router, useForm } from '@inertiajs/react';
import {
Check,
Copy,
Eye,
Radio,
RotateCw,
Save,
ShieldAlert,
StopCircle,
Video,
} from 'lucide-react';
import type { FormEvent, ReactNode } from 'react';
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useClipboard } from '@/hooks/use-clipboard';
import { cn } from '@/lib/utils';
import { dashboard as dashboardRoute } from '@/routes';
import { show as showChannel } from '@/routes/channels';
import {
stop as stopBroadcastRoute,
store as storeBroadcastRoute,
} from '@/routes/creator/broadcasts';
import {
store as storeChannelRoute,
update as updateChannelRoute,
} from '@/routes/creator/channel';
import { rotate as rotateStreamKeyRoute } from '@/routes/creator/stream-key';
import type { BroadcastSummary, Category } from '@/types';
type CreatorChannel = {
id: number;
slug: string;
display_name: string;
description: string | null;
avatar_url: string | null;
banner_url: string | null;
thumbnail_url: string | null;
is_live: boolean;
viewer_count: number;
stream_key_last_used_at: string | null;
category_id: number | null;
live_broadcast: BroadcastSummary | null;
};
type Props = {
channel: CreatorChannel | null;
plainStreamKey: string | null;
streaming: {
ingestServer: string;
tokenizedPath: string | null;
};
categories: Category[];
recentBroadcasts: BroadcastSummary[];
};
export default function Dashboard({
channel,
plainStreamKey,
streaming,
categories,
recentBroadcasts,
}: Props) {
const [copiedText, copy] = useClipboard();
const [stopTarget, setStopTarget] = useState(null);
const liveBroadcast = channel?.live_broadcast ?? null;
const pendingBroadcast =
recentBroadcasts.find((broadcast) => broadcast.status === 'pending') ??
null;
const createForm = useForm({
display_name: '',
slug: '',
description: '',
category_id: '',
});
const channelForm = useForm({
display_name: channel?.display_name ?? '',
slug: channel?.slug ?? '',
description: channel?.description ?? '',
category_id: channel?.category_id ? String(channel.category_id) : '',
});
const broadcastForm = useForm({
title: '',
recording_enabled: false,
});
function createChannel(event: FormEvent) {
event.preventDefault();
createForm.submit(storeChannelRoute());
}
function updateChannel(event: FormEvent) {
event.preventDefault();
channelForm.submit(updateChannelRoute());
}
function createBroadcast(event: FormEvent) {
event.preventDefault();
broadcastForm.submit(storeBroadcastRoute(), {
onSuccess: () => broadcastForm.reset('title', 'recording_enabled'),
});
}
function stopBroadcast() {
if (!stopTarget) {
return;
}
router.post(stopBroadcastRoute(stopTarget.id), undefined, {
onFinish: () => setStopTarget(null),
});
}
return (
<>
{pendingBroadcast && !liveBroadcast && (
READY
)}
{channel?.display_name ?? 'Creator Studio'}
Prepare broadcasts, secure your stream key, and
monitor the current session.
{channel && (
)}
{!channel ? (
) : (
{(liveBroadcast ?? pendingBroadcast) && (
Finish the current broadcast before
preparing another session.
)}
{recentBroadcasts.map((broadcast) => (
setStopTarget(broadcast)
}
/>
))}
{recentBroadcasts.length === 0 && (
)}
)}
>
);
}
Dashboard.layout = {
breadcrumbs: [
{
title: 'Creator Studio',
href: dashboardRoute(),
},
],
};
function Panel({
title,
description,
children,
}: {
title: string;
description?: string;
children: ReactNode;
}) {
return (
{title}
{description && (
{description}
)}
{children}
);
}
function Field({
label,
error,
className,
children,
}: {
label: string;
error?: string;
className?: string;
children: ReactNode;
}) {
return (
);
}
function CategorySelect({
categories,
value,
onChange,
}: {
categories: Category[];
value: string;
onChange: (value: string) => void;
}) {
return (
);
}
function CopyRow({
label,
value,
copied,
muted = false,
onCopy,
}: {
label: string;
value: string;
copied: boolean;
muted?: boolean;
onCopy: (text: string) => Promise;
}) {
return (
{label}
{value}
);
}
function MetricCard({ label, value }: { label: string; value: string }) {
return (
);
}
function BroadcastRow({
broadcast,
onStop,
}: {
broadcast: BroadcastSummary;
onStop: () => void;
}) {
const active = broadcast.status !== 'ended';
return (
{broadcast.title}
{broadcast.status ?? 'unknown'} -{' '}
{broadcast.recording_enabled ? 'recording' : 'live only'}
{broadcast.has_vod ? ' - VOD ready' : ''}
{active && (
)}
);
}
function StatusPill({
live = false,
ready = false,
viewers = 0,
}: {
live?: boolean;
ready?: boolean;
viewers?: number;
}) {
if (live) {
return (
LIVE - {viewers} viewers
);
}
if (ready) {
return (
READY
);
}
return (
OFFLINE
);
}
function ChannelAvatar({ channel }: { channel: CreatorChannel | null }) {
return (
{channel?.avatar_url ? (

) : (
)}
);
}
function ReadinessRow({
done = false,
label,
}: {
done?: boolean;
label: string;
}) {
return (
{done && }
{label}
);
}
function EmptyState({ text }: { text: string }) {
return (
{text}
);
}