2026-03-23 09:57:31 -04:00
|
|
|
import React from 'react';
|
|
|
|
|
import { usePresence } from '../hooks/usePresence';
|
|
|
|
|
import { useAuth } from '../context/AuthContext';
|
|
|
|
|
|
2026-04-05 04:27:07 -04:00
|
|
|
function GearIcon() {
|
|
|
|
|
return (
|
|
|
|
|
<svg className="w-3 h-3 mr-1" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.248a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ChatBubbleIcon() {
|
|
|
|
|
return (
|
|
|
|
|
<svg className="w-3 h-3 mr-1" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
|
|
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ServiceBadge({ name, role }) {
|
|
|
|
|
const isBot = role === 'bot';
|
|
|
|
|
const colorClass = isBot
|
|
|
|
|
? 'bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300'
|
|
|
|
|
: 'bg-teal-100 dark:bg-teal-900/40 text-teal-700 dark:text-teal-300';
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${colorClass}`}>
|
|
|
|
|
{isBot ? <ChatBubbleIcon /> : <GearIcon />}
|
|
|
|
|
{name}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 09:57:31 -04:00
|
|
|
function PresenceBar() {
|
|
|
|
|
const { isAuthenticated } = useAuth();
|
2026-04-05 04:27:07 -04:00
|
|
|
const { viewers, services } = usePresence();
|
2026-03-23 09:57:31 -04:00
|
|
|
|
|
|
|
|
if (!isAuthenticated) return null;
|
|
|
|
|
|
2026-04-05 04:27:07 -04:00
|
|
|
const otherViewers = viewers.filter(v => v.name !== 'me');
|
|
|
|
|
const hasViewers = otherViewers.length > 0;
|
|
|
|
|
const hasServices = services.length > 0;
|
|
|
|
|
|
|
|
|
|
if (!hasViewers && !hasServices) return null;
|
2026-03-23 09:57:31 -04:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="container mx-auto px-2 sm:px-4 pt-3">
|
|
|
|
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 px-4 py-2">
|
2026-04-05 04:27:07 -04:00
|
|
|
<div className="flex items-center gap-4 flex-wrap">
|
|
|
|
|
{hasViewers && (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider font-medium flex-shrink-0">
|
|
|
|
|
who's here?
|
|
|
|
|
</span>
|
|
|
|
|
<div className="flex flex-wrap gap-1.5">
|
|
|
|
|
{viewers.map((v, i) => (
|
|
|
|
|
<span
|
|
|
|
|
key={`${v.name}-${i}`}
|
|
|
|
|
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
|
|
|
|
v.name === 'me'
|
|
|
|
|
? 'bg-indigo-100 dark:bg-indigo-900/40 text-indigo-700 dark:text-indigo-300'
|
|
|
|
|
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{v.name}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
{hasViewers && hasServices && (
|
|
|
|
|
<div className="w-px h-4 bg-gray-300 dark:bg-gray-600" />
|
|
|
|
|
)}
|
|
|
|
|
{hasServices && (
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="text-xs text-gray-500 dark:text-gray-400 uppercase tracking-wider font-medium flex-shrink-0">
|
|
|
|
|
connected
|
2026-03-23 09:57:31 -04:00
|
|
|
</span>
|
2026-04-05 04:27:07 -04:00
|
|
|
<div className="flex flex-wrap gap-1.5">
|
|
|
|
|
{services.map((s, i) => (
|
|
|
|
|
<ServiceBadge key={`${s.name}-${i}`} name={s.name} role={s.role} />
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-03-23 09:57:31 -04:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default PresenceBar;
|