UNPKG

sourabhrealtime

Version:

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

227 lines (204 loc) 7.34 kB
import React, { useState, useEffect } from 'react'; import { useAuth } from './AuthProvider'; export const ApprovalWorkflow = ({ apiUrl = 'http://localhost:3002', projectId, onApprovalChange }) => { const { user, isAdmin, isSuperAdmin } = useAuth(); const [pendingChanges, setPendingChanges] = useState([]); const [loading, setLoading] = useState(false); useEffect(() => { if (isAdmin) { loadPendingChanges(); } }, [projectId, isAdmin]); const loadPendingChanges = async () => { setLoading(true); try { const response = await fetch(`${apiUrl}/api/projects/${projectId}/pending-changes`, { headers: { 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` } }); if (response.ok) { const data = await response.json(); setPendingChanges(data.changes || []); } } catch (error) { console.error('Error loading pending changes:', error); } finally { setLoading(false); } }; const handleApproval = async (changeId, approved) => { try { const response = await fetch(`${apiUrl}/api/changes/${changeId}/approve`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ approved, reviewedBy: user.id, reviewedAt: Date.now() }) }); if (response.ok) { setPendingChanges(prev => prev.filter(change => change.id !== changeId)); onApprovalChange?.(changeId, approved); } } catch (error) { console.error('Error processing approval:', error); } }; if (!isAdmin) { return null; } if (loading) { return <div className="sourabhrealtime-loading">Loading pending changes...</div>; } if (pendingChanges.length === 0) { return ( <div className="sourabhrealtime-approval-workflow"> <h3>Approval Workflow</h3> <p>No pending changes to review.</p> </div> ); } return ( <div className="sourabhrealtime-approval-workflow"> <h3>Pending Changes ({pendingChanges.length})</h3> <div className="sourabhrealtime-changes-list"> {pendingChanges.map(change => ( <div key={change.id} className="sourabhrealtime-change-item"> <div className="sourabhrealtime-change-header"> <div className="sourabhrealtime-change-user"> <strong>{change.user.name}</strong> <span className="sourabhrealtime-change-time"> {new Date(change.timestamp).toLocaleString()} </span> </div> <div className="sourabhrealtime-change-type"> {change.type === 'content' ? 'Content Change' : 'Other Change'} </div> </div> <div className="sourabhrealtime-change-content"> {change.type === 'content' && ( <div className="sourabhrealtime-content-diff"> <div className="sourabhrealtime-diff-section"> <h4>Previous Content:</h4> <pre className="sourabhrealtime-content-preview"> {change.previousContent?.substring(0, 200)} {change.previousContent?.length > 200 && '...'} </pre> </div> <div className="sourabhrealtime-diff-section"> <h4>New Content:</h4> <pre className="sourabhrealtime-content-preview"> {change.newContent?.substring(0, 200)} {change.newContent?.length > 200 && '...'} </pre> </div> </div> )} {change.description && ( <div className="sourabhrealtime-change-description"> <strong>Description:</strong> {change.description} </div> )} </div> <div className="sourabhrealtime-change-actions"> <button onClick={() => handleApproval(change.id, true)} className="sourabhrealtime-button sourabhrealtime-button-success" > Approve </button> <button onClick={() => handleApproval(change.id, false)} className="sourabhrealtime-button sourabhrealtime-button-danger" > Reject </button> </div> </div> ))} </div> </div> ); }; // Enhanced RealtimeEditor with approval workflow export const RealtimeEditorWithApproval = (props) => { const { user, isAdmin } = useAuth(); const [pendingApproval, setPendingApproval] = useState(false); const [approvalMessage, setApprovalMessage] = useState(''); const handleContentChange = async (content, cursorPosition) => { // If user is not admin, changes need approval if (!isAdmin && props.requireApproval) { setPendingApproval(true); setApprovalMessage('Your changes have been submitted for approval.'); // Send change for approval instead of direct update try { await fetch(`${props.apiUrl}/api/changes/submit`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('auth_token')}` }, body: JSON.stringify({ projectId: props.projectId, type: 'content', newContent: content, previousContent: props.currentContent, user: user, timestamp: Date.now(), description: 'Content update' }) }); } catch (error) { console.error('Error submitting change for approval:', error); } return; } // Admin users can make direct changes if (props.onContentChange) { props.onContentChange(content, cursorPosition); } }; return ( <div className="sourabhrealtime-editor-with-approval"> {pendingApproval && ( <div className="sourabhrealtime-approval-notice"> <div className="sourabhrealtime-notice-content"> <span>{approvalMessage}</span> <button onClick={() => setPendingApproval(false)} className="sourabhrealtime-notice-close" > × </button> </div> </div> )} {/* Include the original RealtimeEditor with modified props */} <div className="sourabhrealtime-editor-wrapper"> {/* This would be your existing RealtimeEditor component */} {/* Modified to use handleContentChange instead of direct updates */} </div> {isAdmin && ( <ApprovalWorkflow apiUrl={props.apiUrl} projectId={props.projectId} onApprovalChange={(changeId, approved) => { if (approved && props.onApprovedChange) { props.onApprovedChange(changeId); } }} /> )} </div> ); }; export default ApprovalWorkflow;