✅ ALL FEATURES IMPLEMENTED: 🔐 Advanced Permission System: - Role-based access control (Owner/Editor/Viewer) - Permission middleware for all API endpoints - Granular permissions for collection operations - Activity logging for complete audit trails 🌍 Collection Visibility Types: - Private: Owner-only access - Invite-Only: Controlled collaboration - Public: Community accessible - Dynamic permission checking across all endpoints 📧 Complete Email Integration: - Beautiful HTML invitation templates - Role-based permission descriptions - Personal message support - Accept/decline workflow with proper UX - Bulk invitation system for multiple users 🎨 Rich User Interface: - Permission indicators with tooltips - Activity log component with real-time updates - Collaboration management dashboard - Bulk invite modal with batch processing - Permission gates throughout the UI ⚡ Performance & Security: - Database indexes for optimal queries - Comprehensive error handling - CORS headers and preflight support - JWT-based authentication integration - Cascading deletes and data integrity 🚀 Ready for Production: - All API endpoints protected with permissions - Complete activity logging system - Beautiful email templates with Resend - Responsive UI components - Error handling and loading states This system now provides enterprise-level collaboration features for community-driven collection building! 🎯
170 lines
5.4 KiB
JavaScript
170 lines
5.4 KiB
JavaScript
import { useState } from 'react';
|
|
|
|
export default function PermissionIndicator({ userRole, visibility, showTooltip = true }) {
|
|
const [showDetails, setShowDetails] = useState(false);
|
|
|
|
const getRoleInfo = (role) => {
|
|
switch (role) {
|
|
case 'owner':
|
|
return {
|
|
icon: '👑',
|
|
label: 'Owner',
|
|
color: '#f59e0b',
|
|
permissions: ['View', 'Edit', 'Delete', 'Manage Users', 'Change Visibility']
|
|
};
|
|
case 'editor':
|
|
return {
|
|
icon: '✏️',
|
|
label: 'Editor',
|
|
color: '#10b981',
|
|
permissions: ['View', 'Edit', 'Add/Remove Cards']
|
|
};
|
|
case 'viewer':
|
|
return {
|
|
icon: '👁️',
|
|
label: 'Viewer',
|
|
color: '#6b7280',
|
|
permissions: ['View Only']
|
|
};
|
|
default:
|
|
return {
|
|
icon: '🔒',
|
|
label: 'No Access',
|
|
color: '#ef4444',
|
|
permissions: []
|
|
};
|
|
}
|
|
};
|
|
|
|
const getVisibilityInfo = (vis) => {
|
|
switch (vis) {
|
|
case 'public':
|
|
return {
|
|
icon: '🌍',
|
|
label: 'Public',
|
|
color: '#10b981',
|
|
description: 'Anyone can view this collection'
|
|
};
|
|
case 'invite-only':
|
|
return {
|
|
icon: '👥',
|
|
label: 'Invite Only',
|
|
color: '#f59e0b',
|
|
description: 'Only invited users can access'
|
|
};
|
|
case 'private':
|
|
return {
|
|
icon: '🔒',
|
|
label: 'Private',
|
|
color: '#6b7280',
|
|
description: 'Only you can access this collection'
|
|
};
|
|
default:
|
|
return {
|
|
icon: '❓',
|
|
label: 'Unknown',
|
|
color: '#6b7280',
|
|
description: 'Visibility not set'
|
|
};
|
|
}
|
|
};
|
|
|
|
const roleInfo = getRoleInfo(userRole);
|
|
const visibilityInfo = getVisibilityInfo(visibility);
|
|
|
|
return (
|
|
<div className="flex items-center space-x-2">
|
|
{/* Role Badge */}
|
|
{userRole && (
|
|
<div className="relative">
|
|
<div
|
|
className="flex items-center space-x-1 px-2 py-1 rounded-full text-xs font-medium cursor-pointer"
|
|
style={{
|
|
backgroundColor: `${roleInfo.color}20`,
|
|
color: roleInfo.color,
|
|
border: `1px solid ${roleInfo.color}40`
|
|
}}
|
|
onMouseEnter={() => showTooltip && setShowDetails(true)}
|
|
onMouseLeave={() => setShowDetails(false)}
|
|
>
|
|
<span>{roleInfo.icon}</span>
|
|
<span>{roleInfo.label}</span>
|
|
</div>
|
|
|
|
{/* Role Tooltip */}
|
|
{showDetails && showTooltip && (
|
|
<div
|
|
className="absolute bottom-full left-0 mb-2 p-3 rounded-lg shadow-lg z-10 min-w-48"
|
|
style={{ backgroundColor: 'var(--bg-secondary)', border: '1px solid var(--border)' }}
|
|
>
|
|
<div className="text-sm font-medium mb-2" style={{ color: 'var(--text-primary)' }}>
|
|
{roleInfo.icon} {roleInfo.label} Permissions:
|
|
</div>
|
|
<ul className="text-xs space-y-1" style={{ color: 'var(--text-secondary)' }}>
|
|
{roleInfo.permissions.map((permission, index) => (
|
|
<li key={index} className="flex items-center space-x-1">
|
|
<span className="text-green-500">✓</span>
|
|
<span>{permission}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Visibility Badge */}
|
|
{visibility && (
|
|
<div className="relative">
|
|
<div
|
|
className="flex items-center space-x-1 px-2 py-1 rounded-full text-xs font-medium cursor-pointer"
|
|
style={{
|
|
backgroundColor: `${visibilityInfo.color}20`,
|
|
color: visibilityInfo.color,
|
|
border: `1px solid ${visibilityInfo.color}40`
|
|
}}
|
|
onMouseEnter={() => showTooltip && setShowDetails(true)}
|
|
onMouseLeave={() => setShowDetails(false)}
|
|
>
|
|
<span>{visibilityInfo.icon}</span>
|
|
<span>{visibilityInfo.label}</span>
|
|
</div>
|
|
|
|
{/* Visibility Tooltip */}
|
|
{showDetails && showTooltip && (
|
|
<div
|
|
className="absolute bottom-full right-0 mb-2 p-3 rounded-lg shadow-lg z-10 min-w-48"
|
|
style={{ backgroundColor: 'var(--bg-secondary)', border: '1px solid var(--border)' }}
|
|
>
|
|
<div className="text-sm font-medium mb-1" style={{ color: 'var(--text-primary)' }}>
|
|
{visibilityInfo.icon} {visibilityInfo.label}
|
|
</div>
|
|
<p className="text-xs" style={{ color: 'var(--text-secondary)' }}>
|
|
{visibilityInfo.description}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Utility component for inline permission checks
|
|
export function CanEdit({ userRole, children }) {
|
|
const canEdit = ['owner', 'editor'].includes(userRole);
|
|
return canEdit ? children : null;
|
|
}
|
|
|
|
export function CanManage({ userRole, children }) {
|
|
const canManage = userRole === 'owner';
|
|
return canManage ? children : null;
|
|
}
|
|
|
|
export function PermissionGate({ userRole, requiredRole, children, fallback = null }) {
|
|
const roleHierarchy = { viewer: 1, editor: 2, owner: 3 };
|
|
const userLevel = roleHierarchy[userRole] || 0;
|
|
const requiredLevel = roleHierarchy[requiredRole] || 0;
|
|
|
|
return userLevel >= requiredLevel ? children : fallback;
|
|
}
|