feat(reconnect): replace top banner with animated sidebar overlay (#1510)

This commit is contained in:
klopez4212
2026-07-04 08:37:34 -07:00
committed by GitHub
parent 20d3cdc22f
commit f35aeb7986
7 changed files with 171 additions and 117 deletions
+22 -29
View File
@@ -1,12 +1,13 @@
// biome-ignore format: keep compact to stay within file size limit
import { MessageCirclePlus } from "lucide-react";
import * as React from "react";
import { AnimatePresence } from "motion/react";
import { FeatureGate } from "@/shared/features";
import { SidebarDndContext } from "@/features/sidebar/ui/SidebarDnd";
import type { Workspace } from "@/features/workspaces/types";
import { AddWorkspaceDialog } from "@/features/workspaces/ui/AddWorkspaceDialog";
import { useIsMobile } from "@/shared/hooks/use-mobile";
import { useDeferredLoad } from "@/shared/hooks/useDeferredStartup";
import {
useChannelSections,
@@ -34,7 +35,7 @@ import { CreateChannelDialog } from "@/features/sidebar/ui/CreateChannelDialog";
import { NewDirectMessageDialog } from "@/features/sidebar/ui/NewDirectMessageDialog";
import { SidebarProfileCard } from "@/features/sidebar/ui/SidebarProfileCard";
import { SidebarRelayConnectionCard } from "@/features/sidebar/ui/SidebarRelayConnectionCard";
import { useSidebarRelayConnectionCard } from "@/features/sidebar/ui/useSidebarRelayConnectionCard";
import type { useSidebarRelayConnectionCard } from "@/features/sidebar/ui/useSidebarRelayConnectionCard";
import {
SidebarLoadingContent,
useSidebarLoadingShape,
@@ -62,6 +63,7 @@ import {
SidebarMenu,
SidebarMenuItem,
SidebarRail,
useSidebar,
} from "@/shared/ui/sidebar";
type CollapsibleSidebarGroup =
@@ -84,6 +86,7 @@ type AppSidebarProps = {
isCreatingForum: boolean;
isOpeningDm: boolean;
profile?: Profile;
relayConnectionCard: ReturnType<typeof useSidebarRelayConnectionCard>;
selfPresenceStatus: PresenceStatus;
errorMessage?: string;
selectedChannelId: string | null;
@@ -174,6 +177,7 @@ export function AppSidebar({
isCreatingForum,
isOpeningDm,
profile,
relayConnectionCard,
selfPresenceStatus,
errorMessage,
selectedChannelId,
@@ -225,10 +229,8 @@ export function AppSidebar({
const activeWorkingByChannelId = useActiveWorkingChannelsById();
const { status: updateStatus } = useUpdaterContext();
const canShowSidebarUpdateCard = shouldShowSidebarUpdateCard(updateStatus);
const sidebarRelayConnectionCard = useSidebarRelayConnectionCard(
errorMessage,
activeWorkspace?.relayUrl,
);
const { open: sidebarOpen, openMobile } = useSidebar();
const isMobile = useIsMobile();
const [isSidebarUpdateCardDismissed, setIsSidebarUpdateCardDismissed] =
React.useState(false);
const showSidebarUpdateCard =
@@ -741,8 +743,7 @@ export function AppSidebar({
</>
) : null}
{errorMessage &&
!sidebarRelayConnectionCard.hasRelayUnreachableError ? (
{errorMessage && !relayConnectionCard.hasRelayUnreachableError ? (
<div className="px-3 py-2 text-sm text-destructive">
{errorMessage}
</div>
@@ -762,27 +763,19 @@ export function AppSidebar({
) : null}
<SidebarFooter>
<AnimatePresence>
{sidebarRelayConnectionCard.showSidebarRelayConnectionCard ? (
<SidebarRelayConnectionCard
className="mb-2 group-data-[collapsible=icon]:hidden"
isConnected={
sidebarRelayConnectionCard.isRelayConnectionSuccess
}
isReconnectPending={
sidebarRelayConnectionCard.isRelayReconnectPending
}
isWaitingOnReconnectHook={
sidebarRelayConnectionCard.isWaitingOnReconnectHook
}
onDismiss={
sidebarRelayConnectionCard.onDismissRelayConnectionCard
}
onReconnect={sidebarRelayConnectionCard.onReconnectRelay}
key="sidebar-relay-connection-card"
/>
) : null}
</AnimatePresence>
{relayConnectionCard.showSidebarRelayConnectionCard &&
(isMobile ? openMobile : sidebarOpen) ? (
<SidebarRelayConnectionCard
className="mb-2"
isConnected={relayConnectionCard.isRelayConnectionSuccess}
isReconnectPending={relayConnectionCard.isRelayReconnectPending}
isWaitingOnReconnectHook={
relayConnectionCard.isWaitingOnReconnectHook
}
onDismiss={relayConnectionCard.onDismissRelayConnectionCard}
onReconnect={relayConnectionCard.onReconnectRelay}
/>
) : null}
{showSidebarUpdateCard ? (
<div className="mb-2 group-data-[collapsible=icon]:hidden">
<SidebarUpdateCard
@@ -68,9 +68,16 @@ export function useSidebarRelayConnectionCard(
const hasRelayUnreachableError = errorMessage
? isRelayUnreachableError(errorMessage)
: false;
// True when the error is an application-level issue (e.g. auth rejection)
// rather than a network-level relay-unreachable error. In this case, the
// disconnected state should NOT trigger the reconnect card — the app shows
// a dedicated error path instead.
const hasNonUnreachableError =
Boolean(errorMessage) && !hasRelayUnreachableError;
const isRelayConnectionStateDegraded =
relayConnectionState === "reconnecting" ||
relayConnectionState === "stalled";
relayConnectionState === "stalled" ||
(relayConnectionState === "disconnected" && !hasNonUnreachableError);
const isRelayConnectionConnected = relayConnectionState === "connected";
const isRelayConnectionDisconnected = relayConnectionState === "disconnected";
const [isDismissed, setIsDismissed] = React.useState(false);