mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Merge branch 'feat/graceful-degradation' into feat/on-demand-ai-features
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { CheckCircle2, Loader2, WifiOff } from "lucide-react";
|
||||
import { useConnectionStore } from "@/stores/connection-store";
|
||||
|
||||
export function ConnectionBanner() {
|
||||
const status = useConnectionStore((s) => s.status);
|
||||
|
||||
if (status === "connected") return null;
|
||||
|
||||
const config = {
|
||||
disconnected: {
|
||||
bg: "bg-amber-500 dark:bg-amber-600",
|
||||
text: "text-amber-950 dark:text-amber-50",
|
||||
icon: <Loader2 className="h-4 w-4 animate-spin" />,
|
||||
message: "Reconnecting to server\u2026",
|
||||
},
|
||||
offline: {
|
||||
bg: "bg-amber-500 dark:bg-amber-600",
|
||||
text: "text-amber-950 dark:text-amber-50",
|
||||
icon: <WifiOff className="h-4 w-4" />,
|
||||
message: "You\u2019re offline",
|
||||
},
|
||||
reconnected: {
|
||||
bg: "bg-emerald-500 dark:bg-emerald-600",
|
||||
text: "text-emerald-950 dark:text-emerald-50",
|
||||
icon: <CheckCircle2 className="h-4 w-4" />,
|
||||
message: "Connected",
|
||||
},
|
||||
}[status];
|
||||
|
||||
return (
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
className={`fixed top-0 left-0 right-0 z-[60] flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium transition-transform duration-300 ${config.bg} ${config.text}`}
|
||||
>
|
||||
{config.icon}
|
||||
{config.message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
|
||||
import { useMobile } from "@/hooks/use-mobile";
|
||||
import { apiGet } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useConnectionStore } from "@/stores/connection-store";
|
||||
import { Dropzone } from "../common/dropzone";
|
||||
import { GemLogo } from "../common/gem-logo";
|
||||
import { HelpDialog } from "../help/help-dialog";
|
||||
@@ -25,6 +26,8 @@ export function AppLayout({ children, showToolPanel = true, onFiles }: AppLayout
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
const isMobile = useMobile();
|
||||
const [customLogo, setCustomLogo] = useState(false);
|
||||
const connectionStatus = useConnectionStore((s) => s.status);
|
||||
const bannerVisible = connectionStatus !== "connected";
|
||||
|
||||
useEffect(() => {
|
||||
apiGet<{ settings: Record<string, string> }>("/v1/settings")
|
||||
@@ -33,7 +36,12 @@ export function AppLayout({ children, showToolPanel = true, onFiles }: AppLayout
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen bg-background text-foreground overflow-hidden">
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-screen bg-background text-foreground overflow-hidden",
|
||||
bannerVisible && "pt-9",
|
||||
)}
|
||||
>
|
||||
{/* Desktop sidebar */}
|
||||
{!isMobile && (
|
||||
<Sidebar
|
||||
@@ -92,7 +100,12 @@ export function AppLayout({ children, showToolPanel = true, onFiles }: AppLayout
|
||||
|
||||
{/* Mobile top bar */}
|
||||
{isMobile && (
|
||||
<div className="fixed top-0 left-0 right-0 z-30 bg-background/95 backdrop-blur-sm border-b border-border px-3 py-2 flex items-center gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
"fixed left-0 right-0 z-30 bg-background/95 backdrop-blur-sm border-b border-border px-3 py-2 flex items-center gap-3",
|
||||
bannerVisible ? "top-9" : "top-0",
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setMobileSidebarOpen(true)}
|
||||
|
||||
Reference in New Issue
Block a user