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