PopcornPing / frontend /src /components /Dashboard.jsx
Yash Goyal
Correction
2070fe3
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { roomAPI } from '../utils/api';
import Navbar from './Navbar';
// --- Icons ---
const CopyIcon = ({ className }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
);
const LinkIcon = ({ className }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
);
const LockIcon = ({ className }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
);
const MicIcon = ({ className }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"/><path d="M19 10v2a7 7 0 0 1-14 0v-2"/><line x1="12" x2="12" y1="19" y2="22"/></svg>
);
const TrashIcon = ({ className }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
);
const TrendingUpIcon = ({ className }) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>
);
// --- Components ---
const Toggle = ({ enabled, onChange }) => (
<button
onClick={() => onChange(!enabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none ${
enabled ? 'bg-white' : 'bg-gray-800'
}`}
>
<span
className={`${
enabled ? 'translate-x-6 bg-black' : 'translate-x-1 bg-gray-500'
} inline-block h-4 w-4 transform rounded-full transition-transform`}
/>
</button>
);
const Dashboard = () => {
const [rooms, setRooms] = useState([]);
const [loading, setLoading] = useState(false);
const [generatedCode, setGeneratedCode] = useState('');
const [joinCode, setJoinCode] = useState(''); // New state for joining
const [isPasswordProtected, setIsPasswordProtected] = useState(false);
const [waitingRoom, setWaitingRoom] = useState(false);
const { user } = useAuth();
const navigate = useNavigate();
useEffect(() => {
fetchRooms();
generateNewCode();
}, []);
const generateNewCode = () => {
const code = `J-${Math.floor(100 + Math.random() * 900)}K-${Math.floor(10 + Math.random() * 90)}D`;
setGeneratedCode(code);
};
const fetchRooms = async () => {
try {
const response = await roomAPI.getUserRooms();
setRooms(response.data.rooms);
} catch (error) {
console.error('Error fetching rooms:', error);
}
};
const handleCreateRoom = async () => {
setLoading(true);
const roomName = `Room ${generatedCode}`;
try {
const response = await roomAPI.createRoom({
name: roomName,
settings: {
passwordProtected: isPasswordProtected,
muteOnEntry: waitingRoom
}
});
navigate(`/room/${response.data.room.roomId}`);
} catch (error) {
console.error('Error creating room:', error);
alert('Failed to create room');
} finally {
setLoading(false);
}
};
const handleJoinRoom = () => {
if (!joinCode.trim()) return;
// Check if it's a full URL or just a code
let roomId = joinCode;
try {
if (joinCode.includes('http')) {
const url = new URL(joinCode);
// Assuming path is /room/:id, grab the last segment
const pathParts = url.pathname.split('/');
if (pathParts.length > 0) {
roomId = pathParts[pathParts.length - 1];
}
}
} catch (e) {
// Not a valid URL, treat as code
console.log("Input is not a URL, using as code");
}
// Handle "room/XYZ" case if pasted without protocol
if (roomId.includes('/room/')) {
roomId = roomId.split('/room/')[1];
}
navigate(`/room/${roomId}`);
};
const copyToClipboard = (text) => {
navigator.clipboard.writeText(text);
alert("Copied to clipboard!");
};
const handleDeleteRoom = async (e, roomId) => {
e.stopPropagation();
if(window.confirm("End this session?")) {
try {
await roomAPI.endRoom(roomId);
fetchRooms();
alert("Room ended successfully");
} catch (error) {
console.error("Error ending room:", error);
}
}
};
return (
<div className="min-h-screen bg-black text-white font-sans selection:bg-gray-700">
<Navbar />
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-24 pb-12">
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
{/* --- LEFT COLUMN --- */}
<div className="lg:col-span-8 flex flex-col gap-6">
{/* Active Rooms & Create Button */}
<div className="bg-[#111] border border-[#222] rounded-3xl p-6 md:p-8 flex flex-col md:flex-row gap-8">
<div className="flex-1 flex flex-col">
<div className="flex justify-between items-center mb-6">
<h2 className="text-gray-400 text-sm uppercase tracking-wider font-bold">Active Rooms</h2>
<button onClick={fetchRooms} className="text-xs text-gray-500 hover:text-white transition-colors">
Refresh
</button>
</div>
<div className="flex-1 space-y-3 pr-2 overflow-y-auto max-h-[200px] custom-scrollbar">
{rooms.length > 0 ? (
rooms.map((room) => (
<div key={room._id} className="group flex items-center justify-between p-3 bg-[#1a1a1a] rounded-xl border border-[#2a2a2a] hover:border-gray-500 transition-all cursor-pointer">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-[#222] border border-[#333] flex items-center justify-center">
<span className="text-gray-500 text-[10px] font-bold">RM</span>
</div>
<div className="max-w-[120px] sm:max-w-xs overflow-hidden text-ellipsis whitespace-nowrap">
<h3 className="font-medium text-gray-200 text-sm truncate">{room.name}</h3>
</div>
</div>
<button
onClick={(e) => handleDeleteRoom(e, room.roomId)}
className="text-gray-600 hover:text-red-400 transition-colors p-1"
>
<TrashIcon className="w-4 h-4" />
</button>
</div>
))
) : (
<div className="flex flex-col items-center justify-center h-full min-h-[120px] text-gray-600 border border-dashed border-[#222] rounded-xl bg-[#151515]">
<p className="text-sm">No active rooms</p>
</div>
)}
</div>
</div>
<div className="w-full md:w-64 flex-shrink-0">
<button
onClick={handleCreateRoom}
disabled={loading}
className="w-full h-full min-h-[160px] rounded-2xl bg-gradient-to-b from-[#2a2a2a] to-black border border-gray-700 shadow-lg flex flex-col items-center justify-center gap-3 group hover:border-white/40 transition-all"
>
<div className="w-12 h-12 bg-white rounded-full flex items-center justify-center text-black group-hover:scale-110 transition-transform">
{loading ? (
<div className="animate-spin w-5 h-5 border-2 border-black border-t-transparent rounded-full"></div>
) : (
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 4v16m8-8H4" /></svg>
)}
</div>
<div className="text-center">
<span className="block text-white font-bold text-lg">Create Room</span>
<span className="text-gray-500 text-xs">Private Session</span>
</div>
</button>
</div>
</div>
{/* Share or Join Code Section */}
<div className="bg-[#111] border border-[#222] rounded-3xl p-8 flex flex-col gap-8">
<div className="flex flex-col md:flex-row items-center justify-between gap-8 border-b border-[#222] pb-8">
<div className="space-y-3 text-center md:text-left w-full">
<h2 className="text-lg text-gray-300 font-medium">Share Your Code</h2>
<div>
<span className="text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-gray-100 to-gray-600 tracking-tight block">
{generatedCode}
</span>
</div>
<div className="flex flex-wrap gap-3 mt-4 justify-center md:justify-start">
<button
onClick={() => copyToClipboard(generatedCode)}
className="flex items-center gap-2 px-4 py-2 bg-[#1a1a1a] rounded-lg text-xs text-gray-400 hover:text-white border border-[#333] transition-colors"
>
<CopyIcon className="w-3 h-3" /> Copy Code
</button>
<button
onClick={() => copyToClipboard(`${window.location.origin}/room/${generatedCode}`)}
className="flex items-center gap-2 px-4 py-2 bg-[#1a1a1a] rounded-lg text-xs text-gray-400 hover:text-white border border-[#333] transition-colors"
>
<LinkIcon className="w-3 h-3" /> Copy Link
</button>
</div>
</div>
<div className="w-full md:w-auto flex-shrink-0">
<button
onClick={handleCreateRoom}
className="w-full md:w-48 py-4 bg-white hover:bg-gray-200 text-black font-bold text-lg rounded-xl shadow-lg transition-all"
>
Start Meeting
</button>
</div>
</div>
{/* Join Existing Room Input */}
<div className="flex flex-col md:flex-row items-center gap-4">
<div className="flex-1 w-full">
<label className="text-gray-400 text-xs font-bold uppercase tracking-wider block mb-2">Have a link or code?</label>
<input
type="text"
value={joinCode}
onChange={(e) => setJoinCode(e.target.value)}
placeholder="Enter room code or paste link here"
className="w-full bg-[#1a1a1a] border border-[#333] rounded-xl px-4 py-3 text-white focus:outline-none focus:border-white/20 transition-all placeholder:text-gray-600"
/>
</div>
<button
onClick={handleJoinRoom}
className="w-full md:w-auto px-8 py-3 mt-6 bg-[#2a2a2a] hover:bg-[#3a3a3a] text-white font-bold rounded-xl border border-[#333] transition-all"
>
Join Room
</button>
</div>
</div>
</div>
{/* --- RIGHT COLUMN --- */}
<div className="lg:col-span-4 flex flex-col gap-6">
{/* Room Configuration */}
<div className="bg-[#111] border border-[#222] rounded-3xl p-8 flex flex-col justify-center min-h-[240px]">
<h2 className="text-gray-400 text-sm mb-6 font-bold uppercase tracking-wider">Room Configuration</h2>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<LockIcon className="w-5 h-5 text-gray-500" />
<div className="flex flex-col">
<span className="text-white text-sm font-medium">Require Password</span>
</div>
</div>
<Toggle enabled={isPasswordProtected} onChange={setIsPasswordProtected} />
</div>
<div className="h-px bg-[#222] w-full"></div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<MicIcon className="w-5 h-5 text-gray-500" />
<div className="flex flex-col">
<span className="text-white text-sm font-medium">Waiting Room</span>
</div>
</div>
<Toggle enabled={waitingRoom} onChange={setWaitingRoom} />
</div>
</div>
</div>
{/* Meeting Analytics */}
<div className="bg-[#111] border border-[#222] rounded-3xl p-8">
<h2 className="text-gray-400 text-sm font-bold uppercase tracking-wider mb-1">Statistics</h2>
<p className="text-[11px] text-gray-600 mb-6">Your meeting activity</p>
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Active Rooms</span>
<span className="text-2xl font-bold text-white">{rooms.length}</span>
</div>
<div className="flex items-center justify-between">
<span className="text-sm text-gray-400">Total Created</span>
<span className="text-2xl font-bold text-white">{rooms.length}</span>
</div>
{rooms.length > 0 && (
<div className="mt-4 pt-4 border-t border-[#222]">
<div className="flex items-center justify-between text-xs">
<span className="text-gray-500">Status</span>
<span className="flex items-center gap-1 text-green-400">
<TrendingUpIcon className="w-3 h-3" />
Active
</span>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</main>
</div>
);
};
export default Dashboard;