import { Head, Link, router } from '@inertiajs/react';
import {
Ban,
KeyRound,
Lock,
Radio,
Shield,
StopCircle,
Undo2,
Unlock,
UserCheck,
Users,
UserX,
} from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { useState } from 'react';
import {
updateDefault as updateChannelCreationDefault,
updateUser as updateUserCreatorAccess,
} from '@/actions/App/Http/Controllers/AdminChannelCreationController';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { useTranslation } from '@/lib/translations';
import { dashboard as adminDashboard } from '@/routes/admin';
import { stop as stopBroadcast } from '@/routes/admin/broadcasts';
import { restore, suspend } from '@/routes/admin/channels';
import { show as showChannel } from '@/routes/channels';
type LiveBroadcast = {
id: number;
title: string;
started_at: string | null;
channel: {
id: number;
slug: string;
display_name: string;
thumbnail_url: string | null;
owner: string;
};
};
type AdminChannel = {
id: number;
slug: string;
display_name: string;
avatar_url: string | null;
thumbnail_url: string | null;
is_live: boolean;
suspended_at: string | null;
owner: {
id: number;
name: string;
};
};
type AdminUser = {
id: number;
name: string;
email: string;
is_admin: boolean;
can_create_channel: boolean;
suspended_at: string | null;
created_at: string | null;
channel: {
id: number;
slug: string;
display_name: string;
} | null;
};
type Props = {
stats: {
users: number;
channels: number;
live_channels: number;
vods: number;
creator_access_grants: number;
};
channelCreationPolicy: {
channel_creation_open: boolean;
};
liveBroadcasts: LiveBroadcast[];
channels: AdminChannel[];
users: AdminUser[];
};
type ModerationAction =
| { kind: 'stop'; broadcast: LiveBroadcast }
| { kind: 'suspend'; channel: AdminChannel }
| { kind: 'restore'; channel: AdminChannel }
| null;
export default function AdminDashboard({
stats,
channelCreationPolicy,
liveBroadcasts,
channels,
users,
}: Props) {
const [action, setAction] = useState(null);
const { t } = useTranslation();
function setChannelCreationDefault(channelCreationOpen: boolean) {
router.patch(
updateChannelCreationDefault(),
{
channel_creation_open: channelCreationOpen,
},
{
preserveScroll: true,
},
);
}
function setUserCreatorAccess(user: AdminUser, canCreateChannel: boolean) {
router.patch(
updateUserCreatorAccess(user.id),
{
can_create_channel: canCreateChannel,
},
{
preserveScroll: true,
},
);
}
function confirmAction() {
if (!action) {
return;
}
if (action.kind === 'stop') {
router.post(stopBroadcast(action.broadcast.id), undefined, {
onFinish: () => setAction(null),
});
return;
}
router.post(
action.kind === 'suspend'
? suspend(action.channel.slug)
: restore(action.channel.slug),
undefined,
{ onFinish: () => setAction(null) },
);
}
return (
<>
{t('Moderation console')}
{t(
'Review live streams, suspended states, users, and recordings.',
)}
{t(':count active broadcasts', {
count: liveBroadcasts.length,
})}
{t('Channel creation access')}
{t(
'Control who can create a creator channel.',
)}
setChannelCreationDefault(false)}
>
Approval only
setChannelCreationDefault(true)}
>
Open to all
{users.map((user) => (
setUserCreatorAccess(user, canCreateChannel)
}
/>
))}
{users.length === 0 && (
)}
{t('Live broadcasts')}
{t(':count rows', {
count: liveBroadcasts.length,
})}
{liveBroadcasts.map((broadcast) => (
{broadcast.title}
{t(':channel by :owner', {
channel:
broadcast.channel.display_name,
owner: broadcast.channel.owner,
})}
{t('LIVE')}
Open
setAction({
kind: 'stop',
broadcast,
})
}
>
Stop
))}
{liveBroadcasts.length === 0 && (
)}
{t('Recent channels')}
{t(':count rows', {
count: channels.length,
})}
{channels.map((channel) => (
{channel.display_name}
{t('@:slug owned by :owner', {
slug: channel.slug,
owner: channel.owner.name,
})}
{channel.is_live && (
{t('LIVE')}
)}
{channel.suspended_at && (
{t('SUSPENDED')}
)}
Open
{channel.suspended_at ? (
setAction({
kind: 'restore',
channel,
})
}
>
Restore
) : (
setAction({
kind: 'suspend',
channel,
})
}
>
Suspend
)}
))}
!open && setAction(null)}
>
{t(dialogTitle(action))}
{t(dialogDescription(action))}
setAction(null)}
>
Cancel
Confirm
>
);
}
AdminDashboard.layout = {
breadcrumbs: [
{
title: 'Admin',
href: adminDashboard(),
},
],
};
function Stat({
icon: Icon,
label,
value,
live = false,
}: {
icon: LucideIcon;
label: string;
value: number;
live?: boolean;
}) {
const { t } = useTranslation();
return (
);
}
function CreatorAccessRow({
user,
defaultOpen,
onChange,
}: {
user: AdminUser;
defaultOpen: boolean;
onChange: (canCreateChannel: boolean) => void;
}) {
const effectiveAccess = defaultOpen || user.can_create_channel;
const { t } = useTranslation();
return (
{user.name}
{user.is_admin && (
{t('ADMIN')}
)}
{user.suspended_at && (
{t('SUSPENDED')}
)}
{effectiveAccess
? t('CAN CREATE')
: t('NEEDS APPROVAL')}
{defaultOpen && !user.can_create_channel && (
{t('DEFAULT')}
)}
{user.email}
{user.channel && (
<>
{' - '}
{user.channel.display_name}
>
)}
onChange(!user.can_create_channel)}
>
{user.can_create_channel ? (
) : (
)}
{user.can_create_channel ? 'Remove grant' : 'Grant'}
);
}
function Thumb({ url }: { url: string | null }) {
return (
{url ? (
) : (
)}
);
}
function Avatar({ channel }: { channel: AdminChannel }) {
return (
{channel.avatar_url ? (
) : (
)}
);
}
function EmptyState({ text }: { text: string }) {
const { t } = useTranslation();
return (
{t(text)}
);
}
function dialogTitle(action: ModerationAction): string {
if (action?.kind === 'stop') {
return 'Stop live broadcast?';
}
if (action?.kind === 'restore') {
return 'Restore channel?';
}
return 'Suspend channel?';
}
function dialogDescription(action: ModerationAction): string {
if (action?.kind === 'stop') {
return 'The live session will end immediately and the channel will move offline.';
}
if (action?.kind === 'restore') {
return 'The channel will be visible again if the owning account is active.';
}
return 'The channel will be hidden from public browsing and watch pages.';
}