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
JSX
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;