~3,470 new keys (5,770 total, EN=ES), ~115 more files wired. - nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets (sort/trust/weather), spot-type label helper. - placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications, collectives, crew chat/coordinator. settingsUi: settings, auth/signup, onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints, app shop, newsletter, community stats, leaderboards, profile. - Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import { useUserStatus, SKATING_STATUSES } from '@/hooks/useUserStatus';
|
|
import { useTranslation } from '@/contexts/language';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface UserStatusIndicatorProps {
|
|
pubkey: string;
|
|
className?: string;
|
|
showLabel?: boolean;
|
|
size?: 'sm' | 'md' | 'lg';
|
|
}
|
|
|
|
export function UserStatusIndicator({
|
|
pubkey,
|
|
className,
|
|
showLabel = false,
|
|
size = 'sm',
|
|
}: UserStatusIndicatorProps) {
|
|
const { t } = useTranslation();
|
|
const { data: status, isLoading } = useUserStatus(pubkey);
|
|
|
|
if (isLoading || !status) return null;
|
|
|
|
const preset = status.statusId
|
|
? SKATING_STATUSES.find(s => s.id === status.statusId)
|
|
: null;
|
|
|
|
const emoji = status.emoji || preset?.emoji || '🛹';
|
|
const label = preset?.label || status.status;
|
|
|
|
const sizeClasses = {
|
|
sm: 'text-xs px-1.5 py-0',
|
|
md: 'text-sm px-2 py-0.5',
|
|
lg: 'text-base px-3 py-1',
|
|
};
|
|
|
|
const dotSizeClasses = {
|
|
sm: 'w-2 h-2',
|
|
md: 'w-2.5 h-2.5',
|
|
lg: 'w-3 h-3',
|
|
};
|
|
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<div className={cn('inline-flex items-center gap-1', className)}>
|
|
{/* Pulsing dot indicator */}
|
|
<span className="relative flex">
|
|
<span
|
|
className={cn(
|
|
'animate-ping absolute inline-flex rounded-full bg-green-400 opacity-75',
|
|
dotSizeClasses[size]
|
|
)}
|
|
/>
|
|
<span
|
|
className={cn(
|
|
'relative inline-flex rounded-full bg-green-500',
|
|
dotSizeClasses[size]
|
|
)}
|
|
/>
|
|
</span>
|
|
{showLabel && (
|
|
<Badge
|
|
variant="outline"
|
|
className={cn(
|
|
'border-green-500/50 text-green-400 font-medium',
|
|
sizeClasses[size]
|
|
)}
|
|
>
|
|
{emoji} {label}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top" className="max-w-[200px]">
|
|
<div className="text-center">
|
|
<div className="text-lg mb-1">{emoji}</div>
|
|
<p className="font-bold">{label}</p>
|
|
{status.linkedSpot && (
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{t('socialUi.userStatus.atSpot')}
|
|
</p>
|
|
)}
|
|
{status.expiration && (
|
|
<p className="text-xs text-muted-foreground mt-1">
|
|
{t('socialUi.userStatus.autoClears', { min: Math.round((status.expiration - Date.now() / 1000) / 60) })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
);
|
|
}
|