The entire skate-spots codebase was accidentally copied into src/src/ and public/public/ instead of src/ and public/ directly. This left the real src/ directory as blank MKStack template stubs, explaining why the landing page showed the default template instead of SkateSpots. Changes made: - Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx, all pages, components, hooks, contexts, lib, i18n, test) - Copied all files from public/public/ up to public/ (images, manifest, etc.) - Deleted src/src/ and public/public/ nested directories - Removed HelloWorld.tsx template stub from pages/ - Retained the previously fixed index.css (Tailwind v3 directives) Full skate-spots feature set is now active: - All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.) - NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler - Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.) - Outfit Variable font, Amber pre-check in main.tsx - All hooks (useSkateSpots, useCoaches, useZaps, etc.) - i18n support (en/es) Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
94 lines
2.7 KiB
TypeScript
94 lines
2.7 KiB
TypeScript
import { useUserStatus, SKATING_STATUSES } from '@/hooks/useUserStatus';
|
||
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 { 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">
|
||
📍 At a spot
|
||
</p>
|
||
)}
|
||
{status.expiration && (
|
||
<p className="text-xs text-muted-foreground mt-1">
|
||
⏱️ Auto-clears in {Math.round((status.expiration - Date.now() / 1000) / 60)} min
|
||
</p>
|
||
)}
|
||
</div>
|
||
</TooltipContent>
|
||
</Tooltip>
|
||
</TooltipProvider>
|
||
);
|
||
}
|