| |
| |
| |
|
|
| '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> |
| ); |
| } |
|
|
| |
|
|
| 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> |
| ); |
| } |
|
|
| |
|
|
| 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> |
| ); |
| } |
|
|