SharingStatus Component
The SharingStatus component provides a compact, informative display of access control settings with optional editing capabilities and QR code sharing.
Overview
This component translates access control configurations into user-friendly status displays. It shows sharing permissions with appropriate icons, descriptions, and interactive elements for editing permissions and generating QR codes for shareable resources.
Location
src/components/SharingStatus.tsx
Features
Status Display
- Visual Icons: Contextual icons for each access type
- Descriptive Text: Clear, concise permission descriptions
- Responsive Layout: Adapts to mobile and desktop layouts
- Hover Effects: Interactive feedback for editable states
Access Control Integration
- Multiple Access Types: Support for all AccessControl types
- Group Name Resolution: Displays actual group names instead of IDs
- Domain Information: Shows domain restrictions clearly
- User Count Display: Indicates number of users with access
Interactive Features
- Editable Mode: Optional click-to-edit functionality
- QR Code Generation: Share button for public/shared resources
- Tooltip Support: Additional context on hover
- Modal Integration: Opens editing dialogs
Props Interface
interface SharingStatusProps {
accessControl?: AccessControl;
className?: string;
userDomain?: string;
isEditable?: boolean;
onAccessChange?: (accessControl: AccessControl) => void;
assistantId?: string;
assistantName?: string;
}
Usage Examples
Read-Only Display
import { SharingStatus } from '@/components/SharingStatus';
function AssistantCard({ assistant }) {
return (
<div className="assistant-card">
<h3>{assistant.name}</h3>
<SharingStatus
accessControl={assistant.accessControl}
className="mt-2"
/>
</div>
);
}
Editable with QR Code
function AssistantHeader({ assistant, onUpdate }) {
return (
<div className="header">
<h1>{assistant.name}</h1>
<SharingStatus
accessControl={assistant.accessControl}
userDomain="example.com"
isEditable={true}
onAccessChange={onUpdate}
assistantId={assistant.id}
assistantName={assistant.name}
className="ml-4"
/>
</div>
);
}
Access Type Displays
Private Access
Icon = Lock;
text = 'Private';
description = 'You';
Public Access
Icon = Globe;
text = 'Public';
description = 'Anyone';
Domain Access
Icon = Building;
text = 'Domain';
description = userDomain || 'your organization';
Multiple Domains
Icon = Building;
text = 'Domains';
description = `${count} domain${count !== 1 ? 's' : ''}`;
// Shows domain names if 2 or fewer
if (accessControl.domains && accessControl.domains.length <= 2) {
description += `: ${accessControl.domains.join(', ')}`;
}
Specific Users
Icon = Users;
text = 'Specific Users';
description = `${emailCount} user${emailCount !== 1 ? 's' : ''}`;
Group Access
Icon = Users2;
text = 'Groups';
description = `${groupCount} group${groupCount !== 1 ? 's' : ''}`;
// Shows group names if available and reasonable number
if (groupNames.length <= 2) {
description += `: ${groupNames.join(', ')}`;
}
Group Name Resolution
Fallback Strategy
// Try multiple approaches to get group names
let availableGroups = [];
try {
availableGroups = getAllGroups();
} catch (error) {
console.error("Error getting groups:", error);
try {
availableGroups = predefinedGroups;
} catch (error) {
// Last resort fallback - hardcoded group names
availableGroups = [
{ id: 'alicante', name: 'Alicante Team' },
{ id: 'dev-team', name: 'Development Team' },
{ id: 'external', name: 'External Users' },
{ id: 'engineering-team', name: 'Engineering Team' },
{ id: 'marketing-team', name: 'Marketing Team' },
{ id: 'leadership', name: 'Leadership' }
];
}
}
Name Mapping
const groupNames = accessControl.groups
.map(groupId => {
const group = availableGroups.find(g => g.id === groupId);
return group?.name || groupId;
});
Responsive Layout
Desktop Layout
<div className="hidden sm:flex sm:items-center max-w-[200px] lg:max-w-xs">
<span className="text-xs sm:text-sm text-muted-foreground/80 whitespace-nowrap overflow-hidden text-ellipsis">
• {description}
</span>
</div>
Mobile Layout
<div className="sm:hidden flex-1 min-w-0">
<span className="text-xs text-muted-foreground/80 overflow-hidden text-ellipsis whitespace-nowrap block">
• {description}
</span>
</div>
Interactive States
Clickable Indicator
const isClickable = isEditable && !!onAccessChange;
const cursorClass = isClickable ? 'cursor-pointer hover:bg-muted/40 transition-colors' : '';
const paddingClass = isClickable ? 'px-2 py-1 rounded-md' : '';
Edit Icon
{isClickable && (
<Edit2 className="h-3.5 w-3.5 text-muted-foreground/0 group-hover:text-muted-foreground/80 transition-colors shrink-0" />
)}
QR Code Integration
QR Button Display Logic
// Show QR code button for non-private assistants
{assistantId && assistantName && accessControl.type !== 'private' && (
<Button
variant="ghost"
size="sm"
onClick={() => setQrDialogOpen(true)}
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
>
<QrCode className="h-4 w-4" />
</Button>
)}
QR Code Dialog
<QRCodeDialog
isOpen={qrDialogOpen}
onOpenChange={setQrDialogOpen}
assistantId={assistantId}
assistantName={assistantName}
/>
Dialog Management
Sharing Status Dialog
{isEditable && onAccessChange && (
<SharingStatusDialog
isOpen={dialogOpen}
onOpenChange={setDialogOpen}
accessControl={accessControl}
onAccessChange={onAccessChange}
userDomain={userDomain}
/>
)}
Click Handling
const handleClick = () => {
if (isEditable && onAccessChange) {
setDialogOpen(true);
}
};
Tooltip Integration
Conditional Tooltip
{isClickable ? (
<TooltipProvider>
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>
{statusComponent}
</TooltipTrigger>
<TooltipContent>
Click to edit sharing settings
</TooltipContent>
</Tooltip>
</TooltipProvider>
) : (
statusComponent
)}
Unknown State Handling
if (!accessControl) {
return (
<div className={`flex items-center gap-2 ${className}`}>
<Shield className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" />
<span className="text-xs sm:text-sm font-medium text-muted-foreground">Unknown</span>
<span className="text-xs sm:text-sm text-muted-foreground/80 hidden sm:inline">• Unknown sharing status</span>
</div>
);
}
Dependencies
@/lib/firebase- AccessControl type definition@/lib/groups- Group name resolution@/components/SharingStatusDialog- Edit functionality@/components/QRCodeDialog- QR code generation@/components/ui/*- Button, Tooltip componentslucide-react- Status icons
Accessibility
Semantic Structure
- Button Role: Editable status has proper button semantics
- Tab Index: Keyboard navigation support
- ARIA Labels: Screen reader friendly descriptions
Visual Indicators
- Hover States: Clear interaction feedback
- Focus States: Keyboard focus indicators
- Color Contrast: Sufficient contrast for text and icons
Performance Considerations
Group Resolution Caching
- Error Handling: Graceful fallback for group lookup failures
- Memoization: Could benefit from memoizing group name resolution
- Lazy Loading: Group names resolved only when needed
Responsive Optimization
- Text Truncation: Prevents layout overflow
- Conditional Rendering: Shows appropriate detail level per screen size
- Icon Sizing: Responsive icon dimensions
Related Components
AccessSelector- Configures the access control settingsSharingStatusDialog- Provides editing interfaceQRCodeDialog- Generates sharing QR codes- Group management interfaces
Best Practices
- Provide Context: Include userDomain when available for better descriptions
- Handle Missing Data: Graceful fallbacks for unknown access control
- Responsive Design: Consider mobile layout constraints
- Clear Interactions: Make editable states obvious to users
- Performance: Cache group lookups when possible
- Accessibility: Support keyboard navigation and screen readers