Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c23b2690d1 |
@@ -36,6 +36,7 @@ export default function DashboardPage() {
|
|||||||
const { data: session, isPending } = useSession();
|
const { data: session, isPending } = useSession();
|
||||||
const [rooms, setRooms] = useState<Room[]>([]);
|
const [rooms, setRooms] = useState<Room[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [deleting, setDeleting] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPending && !session) {
|
if (!isPending && !session) {
|
||||||
@@ -63,6 +64,24 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
|
async function handleDelete(e: React.MouseEvent, room: Room) {
|
||||||
|
e.preventDefault(); // prevent Link navigation
|
||||||
|
e.stopPropagation();
|
||||||
|
if (room.status === "ACTIVE") return;
|
||||||
|
if (!confirm(`Удалить комнату "${room.name}"? Это действие необратимо.`)) return;
|
||||||
|
setDeleting(room.id);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/rooms/${room.id}`, { method: "DELETE" });
|
||||||
|
if (res.ok) {
|
||||||
|
setRooms((prev) => prev.filter((r) => r.id !== room.id));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// silent
|
||||||
|
} finally {
|
||||||
|
setDeleting(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-surface-0 flex items-center justify-center">
|
<main className="min-h-screen bg-surface-0 flex items-center justify-center">
|
||||||
@@ -157,15 +176,33 @@ export default function DashboardPage() {
|
|||||||
<Link
|
<Link
|
||||||
key={room.id}
|
key={room.id}
|
||||||
href={`/room/${room.code}`}
|
href={`/room/${room.code}`}
|
||||||
className="block bg-surface-1 border border-border-subtle rounded-xl p-5 hover:border-border-default transition-colors cursor-pointer group"
|
className="block bg-surface-1 border border-border-subtle rounded-xl p-5 hover:border-border-default transition-colors cursor-pointer group relative"
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between mb-4">
|
<div className="flex items-start justify-between mb-4">
|
||||||
<h2 className="font-semibold text-base truncate pr-3 group-hover:text-white transition-colors">
|
<h2 className="font-semibold text-base truncate pr-3 group-hover:text-white transition-colors">
|
||||||
{room.name}
|
{room.name}
|
||||||
</h2>
|
</h2>
|
||||||
<div className={`flex items-center gap-1.5 shrink-0 text-xs font-medium ${statusTextColors[room.status]}`}>
|
<div className="flex items-center gap-2 shrink-0">
|
||||||
<span className={`h-2 w-2 rounded-full ${statusDotColors[room.status]}`} />
|
<div className={`flex items-center gap-1.5 text-xs font-medium ${statusTextColors[room.status]}`}>
|
||||||
{statusLabels[room.status]}
|
<span className={`h-2 w-2 rounded-full ${statusDotColors[room.status]}`} />
|
||||||
|
{statusLabels[room.status]}
|
||||||
|
</div>
|
||||||
|
{room.status !== "ACTIVE" && (
|
||||||
|
<button
|
||||||
|
onClick={(e) => handleDelete(e, room)}
|
||||||
|
disabled={deleting === room.id}
|
||||||
|
className="opacity-0 group-hover:opacity-100 p-1.5 rounded-lg text-text-muted hover:text-danger hover:bg-danger/10 transition-all disabled:opacity-50"
|
||||||
|
title="Удалить комнату"
|
||||||
|
>
|
||||||
|
{deleting === room.id ? (
|
||||||
|
<div className="w-4 h-4 border-2 border-danger border-t-transparent rounded-full animate-spin" />
|
||||||
|
) : (
|
||||||
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 text-sm">
|
<div className="flex items-center gap-3 text-sm">
|
||||||
|
|||||||
@@ -153,13 +153,14 @@ export default function RoomPage() {
|
|||||||
serverUrl={LIVEKIT_URL}
|
serverUrl={LIVEKIT_URL}
|
||||||
token={token}
|
token={token}
|
||||||
connect={true}
|
connect={true}
|
||||||
className="h-screen flex flex-col bg-surface-0"
|
data-lk-theme="default"
|
||||||
|
className="h-screen max-h-screen flex flex-col bg-surface-0 overflow-hidden"
|
||||||
onDisconnected={() => router.push(isHost ? "/dashboard" : "/")}
|
onDisconnected={() => router.push(isHost ? "/dashboard" : "/")}
|
||||||
>
|
>
|
||||||
<RoomAudioRenderer />
|
<RoomAudioRenderer />
|
||||||
|
|
||||||
{/* Top bar — minimal, just room info */}
|
{/* Top bar — minimal, just room info */}
|
||||||
<header className="flex items-center px-4 h-12 bg-surface-1/80 backdrop-blur-sm border-b border-border-subtle shrink-0">
|
<header className="flex items-center px-4 h-12 bg-surface-1/80 backdrop-blur-sm border-b border-border-subtle shrink-0 z-10">
|
||||||
<h1 className="text-white font-semibold text-sm">{room?.name ?? "Комната"}</h1>
|
<h1 className="text-white font-semibold text-sm">{room?.name ?? "Комната"}</h1>
|
||||||
{room && (
|
{room && (
|
||||||
<span className="text-[11px] text-text-muted font-mono bg-surface-2 px-2 py-0.5 rounded-md border border-border-subtle ml-3">
|
<span className="text-[11px] text-text-muted font-mono bg-surface-2 px-2 py-0.5 rounded-md border border-border-subtle ml-3">
|
||||||
@@ -168,28 +169,28 @@ export default function RoomPage() {
|
|||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* Main content area */}
|
{/* Main content area — fixed between header and footer */}
|
||||||
<div className="flex flex-1 min-h-0">
|
<div className="flex flex-1 min-h-0 overflow-hidden">
|
||||||
{/* Video area */}
|
{/* Video area */}
|
||||||
<div className="flex-1 min-w-0 p-2">
|
<div className="flex-1 min-w-0 min-h-0 relative" style={{ contain: "strict" }}>
|
||||||
<VideoArea />
|
<VideoArea />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
{sidebarOpen && (
|
{sidebarOpen && (
|
||||||
<aside className="w-80 shrink-0 border-l border-border-default flex flex-col bg-surface-1">
|
<aside className="w-80 shrink-0 border-l border-border-default flex flex-col bg-surface-1 overflow-hidden">
|
||||||
{isHost && showLobby && room && (
|
{isHost && showLobby && room && (
|
||||||
<div className="border-b border-border-default">
|
<div className="border-b border-border-default shrink-0">
|
||||||
<LobbyManager roomId={room.id} />
|
<LobbyManager roomId={room.id} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isHost && showModeration && room && (
|
{isHost && showModeration && room && (
|
||||||
<div className="border-b border-border-default">
|
<div className="border-b border-border-default shrink-0">
|
||||||
<ModerationPanel roomId={room.id} />
|
<ModerationPanel roomId={room.id} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showChat && room && (
|
{showChat && room && (
|
||||||
<div className="flex-1 min-h-0">
|
<div className="flex-1 min-h-0 overflow-hidden">
|
||||||
<ChatPanel
|
<ChatPanel
|
||||||
roomId={room.id}
|
roomId={room.id}
|
||||||
sessionId={userId ?? "anonymous"}
|
sessionId={userId ?? "anonymous"}
|
||||||
@@ -202,7 +203,7 @@ export default function RoomPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom control bar — Google Meet style */}
|
{/* Bottom control bar — Google Meet style */}
|
||||||
<footer className="flex items-center justify-between px-4 h-16 bg-surface-1 border-t border-border-default shrink-0">
|
<footer className="flex items-center justify-between px-4 h-16 bg-surface-1 border-t border-border-default shrink-0 z-10">
|
||||||
{/* Left: room time or empty */}
|
{/* Left: room time or empty */}
|
||||||
<div className="w-48" />
|
<div className="w-48" />
|
||||||
|
|
||||||
@@ -298,7 +299,7 @@ function VideoArea() {
|
|||||||
// If someone is screen sharing, use focus layout
|
// If someone is screen sharing, use focus layout
|
||||||
if (screenShareTracks.length > 0) {
|
if (screenShareTracks.length > 0) {
|
||||||
return (
|
return (
|
||||||
<FocusLayoutContainer className="h-full">
|
<FocusLayoutContainer className="absolute inset-0">
|
||||||
<CarouselLayout tracks={cameraTracks} className="h-24">
|
<CarouselLayout tracks={cameraTracks} className="h-24">
|
||||||
<ParticipantTile />
|
<ParticipantTile />
|
||||||
</CarouselLayout>
|
</CarouselLayout>
|
||||||
@@ -308,7 +309,7 @@ function VideoArea() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridLayout tracks={cameraTracks} className="h-full">
|
<GridLayout tracks={cameraTracks} className="absolute inset-0">
|
||||||
<ParticipantTile />
|
<ParticipantTile />
|
||||||
</GridLayout>
|
</GridLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user