myagent10101's picture
feat: Complete CodeSync collaborative coding platform
8f9c4ef verified
// ─────────────────────────────────────────────────────────────
// Dashboard β€” Rooms listing page
// ─────────────────────────────────────────────────────────────
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import {
Plus, Code2, Users, Clock, Globe, Lock,
MoreVertical, Trash2, Settings, LogOut,
} from 'lucide-react';
import { roomsApi } from '../../../lib/api';
import { useAuthStore } from '../../../stores/authStore';
import { formatRelativeTime, cn } from '../../../lib/utils';
import type { Room } from '../../../types';
export default function RoomsPage() {
const router = useRouter();
const { user, isAuthenticated, logout } = useAuthStore();
const [rooms, setRooms] = useState<Room[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [showCreateModal, setShowCreateModal] = useState(false);
useEffect(() => {
if (!isAuthenticated) {
router.push('/login');
return;
}
loadRooms();
}, [isAuthenticated]);
const loadRooms = async () => {
try {
const data = await roomsApi.list() as Room[];
setRooms(data);
} catch (error) {
console.error('Failed to load rooms:', error);
} finally {
setIsLoading(false);
}
};
const handleCreateRoom = async (data: any) => {
try {
const room = await roomsApi.create(data) as Room;
setRooms([room, ...rooms]);
setShowCreateModal(false);
router.push(`/room/${room.id}`);
} catch (error) {
console.error('Failed to create room:', error);
}
};
return (
<div className="min-h-screen bg-editor-bg">
{/* Header */}
<header className="border-b border-editor-border bg-editor-surface">
<div className="flex items-center justify-between px-6 py-3 max-w-7xl mx-auto">
<div className="flex items-center gap-3">
<Code2 className="h-6 w-6 text-editor-accent" />
<span className="text-lg font-bold">CodeSync</span>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-full bg-editor-accent flex items-center justify-center text-white text-sm font-bold">
{user?.name?.charAt(0).toUpperCase()}
</div>
<span className="text-sm">{user?.name}</span>
</div>
<button
onClick={() => { logout(); router.push('/login'); }}
className="text-editor-text-muted hover:text-editor-text p-1"
title="Sign out"
>
<LogOut className="h-4 w-4" />
</button>
</div>
</div>
</header>
{/* Content */}
<main className="max-w-7xl mx-auto px-6 py-8">
<div className="flex items-center justify-between mb-8">
<div>
<h1 className="text-2xl font-bold">Your Rooms</h1>
<p className="text-sm text-editor-text-muted mt-1">
Create or join coding rooms to collaborate in real-time
</p>
</div>
<button
className="btn-primary flex items-center gap-2"
onClick={() => setShowCreateModal(true)}
>
<Plus className="h-4 w-4" />
New Room
</button>
</div>
{/* Rooms Grid */}
{isLoading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="rounded-lg border border-editor-border bg-editor-surface p-5 animate-pulse">
<div className="h-4 bg-editor-hover rounded w-3/4 mb-3" />
<div className="h-3 bg-editor-hover rounded w-1/2" />
</div>
))}
</div>
) : rooms.length === 0 ? (
<div className="flex flex-col items-center justify-center py-16 text-center">
<Code2 className="h-12 w-12 text-editor-text-muted mb-4 opacity-50" />
<h3 className="text-lg font-medium mb-1">No rooms yet</h3>
<p className="text-sm text-editor-text-muted mb-6">
Create your first room to start collaborating
</p>
<button className="btn-primary" onClick={() => setShowCreateModal(true)}>
Create Your First Room
</button>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{rooms.map((room) => (
<RoomCard key={room.id} room={room} />
))}
</div>
)}
</main>
{/* Create Room Modal */}
{showCreateModal && (
<CreateRoomModal
onClose={() => setShowCreateModal(false)}
onCreate={handleCreateRoom}
/>
)}
</div>
);
}
// ─── Room Card ───────────────────────────────────────────────
function RoomCard({ room }: { room: Room }) {
return (
<Link
href={`/room/${room.id}`}
className="group rounded-lg border border-editor-border bg-editor-surface p-5 hover:border-editor-accent/50 transition-all hover:shadow-lg hover:shadow-editor-accent/5"
>
<div className="flex items-start justify-between">
<h3 className="font-semibold group-hover:text-editor-accent transition-colors">
{room.name}
</h3>
{room.isPublic ? (
<Globe className="h-4 w-4 text-editor-text-muted" />
) : (
<Lock className="h-4 w-4 text-editor-text-muted" />
)}
</div>
<div className="mt-3 flex items-center gap-3 text-xs text-editor-text-muted">
<span className="flex items-center gap-1">
<Code2 className="h-3 w-3" />
{room.language}
</span>
<span className="flex items-center gap-1">
<Clock className="h-3 w-3" />
{formatRelativeTime(room.updatedAt)}
</span>
</div>
</Link>
);
}
// ─── Create Room Modal ───────────────────────────────────────
function CreateRoomModal({ onClose, onCreate }: { onClose: () => void; onCreate: (data: any) => void }) {
const [name, setName] = useState('');
const [language, setLanguage] = useState('javascript');
const [isPublic, setIsPublic] = useState(false);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!name.trim()) return;
onCreate({ name, language, isPublic });
};
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm" onClick={onClose}>
<div className="w-full max-w-md rounded-lg border border-editor-border bg-editor-surface p-6 shadow-xl" onClick={(e) => e.stopPropagation()}>
<h2 className="text-lg font-bold mb-4">Create New Room</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1.5">Room Name</label>
<input
type="text"
className="input-field w-full"
placeholder="My Awesome Project"
value={name}
onChange={(e) => setName(e.target.value)}
required
autoFocus
/>
</div>
<div>
<label className="block text-sm font-medium mb-1.5">Language</label>
<select
className="input-field w-full"
value={language}
onChange={(e) => setLanguage(e.target.value)}
>
<option value="javascript">JavaScript</option>
<option value="typescript">TypeScript</option>
<option value="python">Python</option>
<option value="cpp">C++</option>
<option value="java">Java</option>
</select>
</div>
<div className="flex items-center gap-2">
<input
type="checkbox"
id="isPublic"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
className="rounded border-editor-border"
/>
<label htmlFor="isPublic" className="text-sm">Make room public</label>
</div>
<div className="flex justify-end gap-3 pt-2">
<button type="button" className="btn-secondary" onClick={onClose}>Cancel</button>
<button type="submit" className="btn-primary">Create Room</button>
</div>
</form>
</div>
</div>
);
}