UNPKG

sourabhrealtime

Version:

ROBUST RICH TEXT EDITOR: Single-pane contentEditable with direct text selection formatting, speech features, undo/redo, professional UI - Perfect TipTap alternative

312 lines (294 loc) 10.5 kB
import React, { useState, useEffect } from 'react'; import { useAuth } from './AuthProvider'; export const ProjectManager = ({ apiUrl = 'http://localhost:3002', onProjectSelect, onProjectCreate }) => { const { user, isAdmin, isSuperAdmin } = useAuth(); const [projects, setProjects] = useState([]); const [users, setUsers] = useState([]); const [invitations, setInvitations] = useState([]); const [loading, setLoading] = useState(true); const [showCreateForm, setShowCreateForm] = useState(false); const [showInviteForm, setShowInviteForm] = useState(null); const [newProject, setNewProject] = useState({ name: '', description: '', isPublic: false }); const [inviteData, setInviteData] = useState({ email: '', role: 'editor' }); useEffect(() => { loadData(); }, []); const loadData = async () => { setLoading(true); try { // Load projects, users, and invitations const [projectsRes, usersRes, invitationsRes] = await Promise.all([ fetch(`${apiUrl}/projects`), isSuperAdmin ? fetch(`${apiUrl}/admin/users`) : Promise.resolve({ json: () => ({ users: [] }) }), fetch(`${apiUrl}/invitations`) ]); const projectsData = await projectsRes.json(); const usersData = await usersRes.json(); const invitationsData = await invitationsRes.json(); setProjects(projectsData.projects || []); setUsers(usersData.users || []); setInvitations(invitationsData.invitations || []); } catch (error) { console.error('Error loading data:', error); } finally { setLoading(false); } }; const createProject = async (e) => { e.preventDefault(); try { const response = await fetch(`${apiUrl}/api/projects`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ ...newProject, createdBy: user.id }) }); if (response.ok) { const project = await response.json(); setProjects(prev => [...prev, project]); setNewProject({ name: '', description: '', isPublic: false }); setShowCreateForm(false); onProjectCreate?.(project); } } catch (error) { console.error('Error creating project:', error); } }; const inviteToProject = async (projectId, e) => { e.preventDefault(); try { const response = await fetch(`${apiUrl}/api/invitations`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ projectId, email: inviteData.email, role: inviteData.role, invitedBy: user.id }) }); if (response.ok) { setInviteData({ email: '', role: 'editor' }); setShowInviteForm(null); loadData(); // Refresh data } } catch (error) { console.error('Error sending invitation:', error); } }; if (loading) { return <div className="sourabhrealtime-loading">Loading...</div>; } return ( <div className="sourabhrealtime-project-manager"> <div className="sourabhrealtime-header"> <h2>Project Dashboard</h2> <div className="sourabhrealtime-user-info"> <span>Welcome, {user.name}</span> <span className="sourabhrealtime-role-badge">{user.role}</span> </div> </div> {/* Super Admin Stats */} {isSuperAdmin && ( <div className="sourabhrealtime-stats"> <div className="sourabhrealtime-stat-card"> <h3>Total Users</h3> <p>{users.length}</p> </div> <div className="sourabhrealtime-stat-card"> <h3>Total Projects</h3> <p>{projects.length}</p> </div> <div className="sourabhrealtime-stat-card"> <h3>Pending Invitations</h3> <p>{invitations.length}</p> </div> </div> )} {/* Create Project Button */} {isAdmin && ( <div className="sourabhrealtime-actions"> <button onClick={() => setShowCreateForm(true)} className="sourabhrealtime-button sourabhrealtime-button-primary" > Create New Project </button> </div> )} {/* Create Project Form */} {showCreateForm && ( <div className="sourabhrealtime-modal"> <div className="sourabhrealtime-modal-content"> <h3>Create New Project</h3> <form onSubmit={createProject}> <div className="sourabhrealtime-form-group"> <label>Project Name</label> <input type="text" value={newProject.name} onChange={(e) => setNewProject(prev => ({ ...prev, name: e.target.value }))} required /> </div> <div className="sourabhrealtime-form-group"> <label>Description</label> <textarea value={newProject.description} onChange={(e) => setNewProject(prev => ({ ...prev, description: e.target.value }))} /> </div> <div className="sourabhrealtime-form-group"> <label> <input type="checkbox" checked={newProject.isPublic} onChange={(e) => setNewProject(prev => ({ ...prev, isPublic: e.target.checked }))} /> Public Project </label> </div> <div className="sourabhrealtime-form-actions"> <button type="submit" className="sourabhrealtime-button sourabhrealtime-button-primary"> Create Project </button> <button type="button" onClick={() => setShowCreateForm(false)} className="sourabhrealtime-button sourabhrealtime-button-secondary" > Cancel </button> </div> </form> </div> </div> )} {/* Projects List */} <div className="sourabhrealtime-projects-grid"> {projects.map(project => ( <div key={project.id} className="sourabhrealtime-project-card"> <h3>{project.name}</h3> <p>{project.description}</p> <div className="sourabhrealtime-project-meta"> <span>Created: {new Date(project.createdAt).toLocaleDateString()}</span> {project.isPublic && <span className="sourabhrealtime-public-badge">Public</span>} </div> <div className="sourabhrealtime-project-actions"> <button onClick={() => onProjectSelect?.(project)} className="sourabhrealtime-button sourabhrealtime-button-primary" > Open Project </button> {isAdmin && ( <button onClick={() => setShowInviteForm(project.id)} className="sourabhrealtime-button sourabhrealtime-button-secondary" > Invite Users </button> )} </div> </div> ))} </div> {/* Invite Form Modal */} {showInviteForm && ( <div className="sourabhrealtime-modal"> <div className="sourabhrealtime-modal-content"> <h3>Invite User to Project</h3> <form onSubmit={(e) => inviteToProject(showInviteForm, e)}> <div className="sourabhrealtime-form-group"> <label>Email Address</label> <input type="email" value={inviteData.email} onChange={(e) => setInviteData(prev => ({ ...prev, email: e.target.value }))} required /> </div> <div className="sourabhrealtime-form-group"> <label>Role</label> <select value={inviteData.role} onChange={(e) => setInviteData(prev => ({ ...prev, role: e.target.value }))} > <option value="viewer">Viewer</option> <option value="editor">Editor</option> <option value="admin">Admin</option> </select> </div> <div className="sourabhrealtime-form-actions"> <button type="submit" className="sourabhrealtime-button sourabhrealtime-button-primary"> Send Invitation </button> <button type="button" onClick={() => setShowInviteForm(null)} className="sourabhrealtime-button sourabhrealtime-button-secondary" > Cancel </button> </div> </form> </div> </div> )} {/* Super Admin User Management */} {isSuperAdmin && ( <div className="sourabhrealtime-user-management"> <h3>User Management</h3> <div className="sourabhrealtime-users-table"> <table> <thead> <tr> <th>Name</th> <th>Email</th> <th>Role</th> <th>Created</th> <th>Actions</th> </tr> </thead> <tbody> {users.map(u => ( <tr key={u.id}> <td>{u.name}</td> <td>{u.email}</td> <td>{u.role}</td> <td>{new Date(u.createdAt).toLocaleDateString()}</td> <td> <button className="sourabhrealtime-button sourabhrealtime-button-small"> Edit </button> </td> </tr> ))} </tbody> </table> </div> </div> )} </div> ); }; export default ProjectManager;