brackt/app/components/draft/ConnectionOverlay.tsx
Chris Parsons 5a455e6cae Show "Syncing Draft State..." overlay during draft reconnection
When a user reconnects to a draft room (socket reconnect, tab visibility
return, etc.), there was a gap between when the socket connected and when
the full draft state sync arrived. The ConnectionOverlay vanished instantly
on reconnect, showing potentially stale data.

Add an isSyncing flag that keeps the overlay visible with "Syncing Draft
State..." messaging until the actual data arrives via socket or HTTP
revalidation. A 5-second safety timeout prevents the overlay from getting
stuck.

Fixes #78
2026-04-30 20:23:52 -07:00

107 lines
3.5 KiB
TypeScript

import { Button } from "~/components/ui/button";
import { Card } from "~/components/ui/card";
interface ConnectionOverlayProps {
isConnected: boolean;
isReconnecting: boolean;
connectionError: string | null;
isSyncing: boolean;
}
export function ConnectionOverlay({
isConnected,
isReconnecting,
connectionError,
isSyncing,
}: ConnectionOverlayProps) {
if (isConnected && !isSyncing) {
return null;
}
return (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-[100]">
<Card className="w-full max-w-md mx-4 p-8 text-center">
<div className="flex flex-col items-center gap-6">
{/* Spinner or Error Icon */}
{connectionError ? (
<div className="w-16 h-16 rounded-full bg-destructive/15 flex items-center justify-center">
<svg
className="w-8 h-8 text-destructive"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
) : (
<div className="relative w-16 h-16">
<div className="absolute inset-0 border-4 border-primary/30 rounded-full" />
<div className="absolute inset-0 border-4 border-primary border-t-transparent rounded-full animate-spin" />
</div>
)}
{/* Title */}
<div>
<h2 className="text-2xl font-bold mb-2">
{connectionError
? "Connection Error"
: isSyncing
? "Syncing Draft State..."
: isReconnecting
? "Reconnecting..."
: "Connecting to Draft"}
</h2>
<p className="text-muted-foreground">
{connectionError ? (
connectionError
) : isSyncing ? (
"Reconnected. Syncing the latest draft state..."
) : isReconnecting ? (
"Lost connection to the draft server. Attempting to reconnect..."
) : (
"Please wait while we connect you to the live draft room."
)}
</p>
</div>
{/* Action Button (only on persistent error) */}
{connectionError && (
<Button
onClick={() => window.location.reload()}
className="w-full"
variant="default"
>
Reload Page
</Button>
)}
{/* Loading Dots */}
{!connectionError && (
<div className="flex gap-1.5">
<div
className="w-2 h-2 bg-primary rounded-full animate-bounce"
style={{ animationDelay: "0ms" }}
/>
<div
className="w-2 h-2 bg-primary rounded-full animate-bounce"
style={{ animationDelay: "150ms" }}
/>
<div
className="w-2 h-2 bg-primary rounded-full animate-bounce"
style={{ animationDelay: "300ms" }}
/>
</div>
)}
</div>
</Card>
</div>
);
}