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>
115 lines
4.2 KiB
TypeScript
115 lines
4.2 KiB
TypeScript
import { useOnlineStatus, usePendingPostsCount, useSyncPendingPosts } from '@/hooks/useOfflineCache';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
import { Wifi, WifiOff, Cloud, CloudOff, Loader2 } from 'lucide-react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface OfflineIndicatorProps {
|
|
className?: string;
|
|
showWhenOnline?: boolean;
|
|
}
|
|
|
|
export function OfflineIndicator({ className, showWhenOnline = false }: OfflineIndicatorProps) {
|
|
const isOnline = useOnlineStatus();
|
|
const { data: pendingCount } = usePendingPostsCount();
|
|
const { isSyncing } = useSyncPendingPosts();
|
|
|
|
// Don't show when online (unless explicitly requested or there are pending posts)
|
|
if (isOnline && !showWhenOnline && !pendingCount && !isSyncing) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className={cn(
|
|
'gap-2 h-8 px-3 font-medium',
|
|
isOnline
|
|
? 'text-green-400 hover:text-green-300 hover:bg-green-500/10'
|
|
: 'text-orange-400 hover:text-orange-300 hover:bg-orange-500/10 animate-pulse',
|
|
className
|
|
)}
|
|
>
|
|
{isSyncing ? (
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
) : isOnline ? (
|
|
<Wifi className="h-4 w-4" />
|
|
) : (
|
|
<WifiOff className="h-4 w-4" />
|
|
)}
|
|
{!isOnline && <span className="text-xs">Offline</span>}
|
|
{pendingCount !== undefined && pendingCount > 0 && (
|
|
<Badge variant="secondary" className="h-5 px-1.5 text-xs bg-orange-500/20 text-orange-400">
|
|
{pendingCount}
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent align="end" className="w-72 bg-black border-gray-700">
|
|
<div className="space-y-4">
|
|
{/* Connection Status */}
|
|
<div className="flex items-center gap-3">
|
|
<div className={cn(
|
|
'w-10 h-10 rounded-full flex items-center justify-center',
|
|
isOnline ? 'bg-green-500/20' : 'bg-orange-500/20'
|
|
)}>
|
|
{isOnline ? (
|
|
<Cloud className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<CloudOff className="h-5 w-5 text-orange-400" />
|
|
)}
|
|
</div>
|
|
<div>
|
|
<p className="font-bold text-white">
|
|
{isOnline ? 'Connected' : 'Offline Mode'}
|
|
</p>
|
|
<p className="text-xs text-gray-400">
|
|
{isOnline
|
|
? 'All features available'
|
|
: 'Browsing cached spots'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Pending Posts */}
|
|
{pendingCount !== undefined && pendingCount > 0 && (
|
|
<div className="p-3 rounded-lg bg-orange-500/10 border border-orange-500/30">
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-sm text-orange-400 font-medium">
|
|
{pendingCount} pending post{pendingCount > 1 ? 's' : ''}
|
|
</span>
|
|
{isSyncing && (
|
|
<Badge className="bg-orange-500 text-white text-xs">
|
|
<Loader2 className="h-3 w-3 mr-1 animate-spin" />
|
|
Syncing
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
<p className="text-xs text-gray-400 mt-1">
|
|
{isOnline
|
|
? 'Publishing when connection stabilizes...'
|
|
: 'Will be published when back online'}
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* Offline Features Info */}
|
|
{!isOnline && (
|
|
<div className="text-xs text-gray-400 space-y-1">
|
|
<p className="font-medium text-gray-300">Available offline:</p>
|
|
<ul className="list-disc list-inside space-y-0.5">
|
|
<li>Browse previously viewed spots</li>
|
|
<li>View cached spot details</li>
|
|
<li>Queue posts for later</li>
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
}
|