2026-03-11 10:01:37 -07:00
|
|
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
2026-03-09 14:49:50 -07:00
|
|
|
import * as React from "react";
|
2026-03-10 17:11:49 -07:00
|
|
|
import { Settings2 } from "lucide-react";
|
2026-03-09 14:49:50 -07:00
|
|
|
|
2026-03-09 13:02:11 -07:00
|
|
|
import { ChatHeader } from "@/features/chat/ui/ChatHeader";
|
2026-03-09 14:49:50 -07:00
|
|
|
import {
|
2026-03-09 17:33:13 -07:00
|
|
|
useCreateChannelMutation,
|
2026-03-09 14:49:50 -07:00
|
|
|
useChannelsQuery,
|
|
|
|
|
useSelectedChannel,
|
|
|
|
|
} from "@/features/channels/hooks";
|
2026-03-11 11:11:06 -07:00
|
|
|
import { useUnreadChannels } from "@/features/channels/useUnreadChannels";
|
2026-03-10 17:11:49 -07:00
|
|
|
import { ChannelManagementSheet } from "@/features/channels/ui/ChannelManagementSheet";
|
2026-03-09 19:32:56 -07:00
|
|
|
import { useHomeFeedQuery } from "@/features/home/hooks";
|
|
|
|
|
import { HomeView } from "@/features/home/ui/HomeView";
|
2026-03-09 14:49:50 -07:00
|
|
|
import {
|
|
|
|
|
useChannelMessagesQuery,
|
2026-03-10 13:54:25 -07:00
|
|
|
mergeMessages,
|
2026-03-09 14:49:50 -07:00
|
|
|
useSendMessageMutation,
|
2026-03-11 12:09:09 -07:00
|
|
|
useChannelSubscription,
|
2026-03-09 14:49:50 -07:00
|
|
|
} from "@/features/messages/hooks";
|
2026-03-11 12:09:09 -07:00
|
|
|
import {
|
|
|
|
|
collectMessageAuthorPubkeys,
|
|
|
|
|
formatTimelineMessages,
|
|
|
|
|
} from "@/features/messages/lib/formatTimelineMessages";
|
|
|
|
|
import { useProfileQuery, useUsersBatchQuery } from "@/features/profile/hooks";
|
2026-03-09 16:34:56 -07:00
|
|
|
import { MessageComposer } from "@/features/messages/ui/MessageComposer";
|
|
|
|
|
import { MessageTimeline } from "@/features/messages/ui/MessageTimeline";
|
2026-03-10 13:54:25 -07:00
|
|
|
import { SearchDialog } from "@/features/search/ui/SearchDialog";
|
2026-03-11 10:34:41 -07:00
|
|
|
import { SettingsView } from "@/features/settings/ui/SettingsView";
|
2026-03-09 13:02:11 -07:00
|
|
|
import { AppSidebar } from "@/features/sidebar/ui/AppSidebar";
|
2026-03-10 13:54:25 -07:00
|
|
|
import { getEventById } from "@/shared/api/tauri";
|
2026-03-09 14:49:50 -07:00
|
|
|
import { useIdentityQuery } from "@/shared/api/hooks";
|
2026-03-10 13:54:25 -07:00
|
|
|
import type { RelayEvent, SearchHit } from "@/shared/api/types";
|
2026-03-10 17:11:49 -07:00
|
|
|
import { Button } from "@/shared/ui/button";
|
2026-03-11 10:01:37 -07:00
|
|
|
import {
|
|
|
|
|
SidebarInset,
|
|
|
|
|
SidebarProvider,
|
|
|
|
|
SidebarTrigger,
|
|
|
|
|
} from "@/shared/ui/sidebar";
|
2026-03-09 13:02:11 -07:00
|
|
|
|
2026-03-11 10:34:41 -07:00
|
|
|
type AppView = "home" | "channel" | "settings";
|
2026-03-09 19:32:56 -07:00
|
|
|
|
2026-03-10 13:54:25 -07:00
|
|
|
function createSearchAnchorEvent(hit: SearchHit): RelayEvent {
|
|
|
|
|
return {
|
|
|
|
|
id: hit.eventId,
|
|
|
|
|
pubkey: hit.pubkey,
|
|
|
|
|
created_at: hit.createdAt,
|
|
|
|
|
kind: hit.kind,
|
2026-03-10 15:26:19 -07:00
|
|
|
tags: [["h", hit.channelId]],
|
2026-03-10 13:54:25 -07:00
|
|
|
content: hit.content,
|
|
|
|
|
sig: "",
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 13:02:11 -07:00
|
|
|
export function AppShell() {
|
2026-03-09 19:32:56 -07:00
|
|
|
const [selectedView, setSelectedView] = React.useState<AppView>("home");
|
2026-03-10 17:11:49 -07:00
|
|
|
const [isChannelManagementOpen, setIsChannelManagementOpen] =
|
|
|
|
|
React.useState(false);
|
2026-03-10 13:54:25 -07:00
|
|
|
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
|
|
|
|
|
const [searchAnchor, setSearchAnchor] = React.useState<SearchHit | null>(
|
|
|
|
|
null,
|
|
|
|
|
);
|
|
|
|
|
const [searchAnchorChannelId, setSearchAnchorChannelId] = React.useState<
|
|
|
|
|
string | null
|
|
|
|
|
>(null);
|
|
|
|
|
const [searchAnchorEvent, setSearchAnchorEvent] =
|
|
|
|
|
React.useState<RelayEvent | null>(null);
|
2026-03-09 14:49:50 -07:00
|
|
|
const identityQuery = useIdentityQuery();
|
2026-03-11 12:09:09 -07:00
|
|
|
const profileQuery = useProfileQuery();
|
2026-03-09 19:32:56 -07:00
|
|
|
const homeFeedQuery = useHomeFeedQuery();
|
2026-03-09 14:49:50 -07:00
|
|
|
const channelsQuery = useChannelsQuery();
|
|
|
|
|
const channels = channelsQuery.data ?? [];
|
|
|
|
|
const { selectedChannel, setSelectedChannelId } = useSelectedChannel(
|
|
|
|
|
channels,
|
|
|
|
|
null,
|
|
|
|
|
);
|
2026-03-09 17:33:13 -07:00
|
|
|
const createChannelMutation = useCreateChannelMutation();
|
2026-03-09 19:32:56 -07:00
|
|
|
const activeChannel = selectedView === "channel" ? selectedChannel : null;
|
2026-03-11 11:11:06 -07:00
|
|
|
const { unreadChannelIds } = useUnreadChannels(channels, activeChannel);
|
2026-03-09 14:49:50 -07:00
|
|
|
|
2026-03-09 19:32:56 -07:00
|
|
|
const messagesQuery = useChannelMessagesQuery(activeChannel);
|
|
|
|
|
useChannelSubscription(activeChannel);
|
2026-03-09 14:49:50 -07:00
|
|
|
|
|
|
|
|
const sendMessageMutation = useSendMessageMutation(
|
2026-03-09 19:32:56 -07:00
|
|
|
activeChannel,
|
2026-03-09 14:49:50 -07:00
|
|
|
identityQuery.data,
|
|
|
|
|
);
|
2026-03-09 19:32:56 -07:00
|
|
|
const homeUrgentCount =
|
|
|
|
|
(homeFeedQuery.data?.feed.mentions.length ?? 0) +
|
|
|
|
|
(homeFeedQuery.data?.feed.needsAction.length ?? 0);
|
|
|
|
|
const availableChannelIds = React.useMemo(
|
|
|
|
|
() => new Set(channels.map((channel) => channel.id)),
|
|
|
|
|
[channels],
|
|
|
|
|
);
|
2026-03-10 13:54:25 -07:00
|
|
|
const resolvedMessages = React.useMemo(() => {
|
|
|
|
|
const currentMessages = messagesQuery.data ?? [];
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!activeChannel ||
|
|
|
|
|
!searchAnchorEvent ||
|
|
|
|
|
searchAnchorChannelId !== activeChannel.id
|
|
|
|
|
) {
|
|
|
|
|
return currentMessages;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mergeMessages(currentMessages, searchAnchorEvent);
|
|
|
|
|
}, [
|
|
|
|
|
activeChannel,
|
|
|
|
|
messagesQuery.data,
|
|
|
|
|
searchAnchorChannelId,
|
|
|
|
|
searchAnchorEvent,
|
|
|
|
|
]);
|
2026-03-11 12:09:09 -07:00
|
|
|
const messageAuthorPubkeys = React.useMemo(
|
|
|
|
|
() => collectMessageAuthorPubkeys(resolvedMessages),
|
|
|
|
|
[resolvedMessages],
|
|
|
|
|
);
|
|
|
|
|
const messageProfilesQuery = useUsersBatchQuery(messageAuthorPubkeys, {
|
|
|
|
|
enabled: resolvedMessages.length > 0,
|
|
|
|
|
});
|
2026-03-09 14:49:50 -07:00
|
|
|
|
|
|
|
|
const timelineMessages = React.useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
formatTimelineMessages(
|
2026-03-10 13:54:25 -07:00
|
|
|
resolvedMessages,
|
2026-03-09 19:32:56 -07:00
|
|
|
activeChannel,
|
2026-03-09 14:49:50 -07:00
|
|
|
identityQuery.data?.pubkey,
|
2026-03-11 12:09:09 -07:00
|
|
|
profileQuery.data?.avatarUrl ?? null,
|
|
|
|
|
messageProfilesQuery.data?.profiles,
|
2026-03-09 14:49:50 -07:00
|
|
|
),
|
2026-03-11 12:09:09 -07:00
|
|
|
[
|
|
|
|
|
activeChannel,
|
|
|
|
|
identityQuery.data?.pubkey,
|
|
|
|
|
profileQuery.data?.avatarUrl,
|
|
|
|
|
messageProfilesQuery.data?.profiles,
|
|
|
|
|
resolvedMessages,
|
|
|
|
|
],
|
2026-03-09 14:49:50 -07:00
|
|
|
);
|
|
|
|
|
|
2026-03-09 19:32:56 -07:00
|
|
|
const channelDescription = activeChannel
|
2026-03-10 17:11:49 -07:00
|
|
|
? [
|
|
|
|
|
activeChannel.archivedAt ? "Archived." : null,
|
|
|
|
|
activeChannel.topic,
|
|
|
|
|
activeChannel.description,
|
|
|
|
|
activeChannel.purpose,
|
|
|
|
|
activeChannel.channelType === "forum"
|
|
|
|
|
? "Forum channels are listed, but this first pass only wires message streams and DMs."
|
|
|
|
|
: null,
|
|
|
|
|
]
|
|
|
|
|
.filter((value) => value && value.trim().length > 0)
|
|
|
|
|
.join(" ") || "Channel details and activity."
|
2026-03-09 14:49:50 -07:00
|
|
|
: "Connect to the relay to browse channels and read messages.";
|
2026-03-10 09:46:08 -07:00
|
|
|
const contentPaneKey =
|
2026-03-11 10:34:41 -07:00
|
|
|
selectedView === "home"
|
|
|
|
|
? "home"
|
|
|
|
|
: selectedView === "settings"
|
|
|
|
|
? "settings"
|
|
|
|
|
: `channel:${activeChannel?.id ?? "none"}`;
|
2026-03-10 13:54:25 -07:00
|
|
|
const isTimelineLoading =
|
|
|
|
|
messagesQuery.isLoading && resolvedMessages.length === 0;
|
|
|
|
|
|
|
|
|
|
const handleOpenChannel = React.useCallback(
|
|
|
|
|
(channelId: string) => {
|
|
|
|
|
React.startTransition(() => {
|
|
|
|
|
setSelectedChannelId(channelId);
|
|
|
|
|
setSelectedView("channel");
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[setSelectedChannelId],
|
|
|
|
|
);
|
|
|
|
|
|
2026-03-11 10:34:41 -07:00
|
|
|
const handleOpenSettings = React.useCallback(() => {
|
|
|
|
|
setIsSearchOpen(false);
|
|
|
|
|
setIsChannelManagementOpen(false);
|
|
|
|
|
|
|
|
|
|
React.startTransition(() => {
|
|
|
|
|
setSelectedView("settings");
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-03-10 13:54:25 -07:00
|
|
|
const handleOpenSearchResult = React.useCallback(
|
|
|
|
|
(hit: SearchHit) => {
|
|
|
|
|
setSearchAnchor(hit);
|
|
|
|
|
setSearchAnchorChannelId(hit.channelId);
|
|
|
|
|
setSearchAnchorEvent(createSearchAnchorEvent(hit));
|
|
|
|
|
handleOpenChannel(hit.channelId);
|
|
|
|
|
|
|
|
|
|
void getEventById(hit.eventId)
|
|
|
|
|
.then((event) => {
|
|
|
|
|
setSearchAnchorEvent((current) => {
|
|
|
|
|
if (current?.id !== hit.eventId) {
|
|
|
|
|
return current;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return event;
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
console.error(
|
|
|
|
|
"Failed to load search result event",
|
|
|
|
|
hit.eventId,
|
|
|
|
|
error,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[handleOpenChannel],
|
|
|
|
|
);
|
2026-03-09 14:49:50 -07:00
|
|
|
|
2026-03-11 10:34:41 -07:00
|
|
|
React.useEffect(() => {
|
|
|
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
|
|
|
const isSettingsShortcut =
|
|
|
|
|
(event.key === "," || event.code === "Comma") &&
|
|
|
|
|
(event.metaKey || event.ctrlKey) &&
|
|
|
|
|
!event.altKey &&
|
|
|
|
|
!event.shiftKey;
|
|
|
|
|
|
|
|
|
|
if (!isSettingsShortcut) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
handleOpenSettings();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
window.addEventListener("keydown", handleKeyDown);
|
|
|
|
|
return () => {
|
|
|
|
|
window.removeEventListener("keydown", handleKeyDown);
|
|
|
|
|
};
|
|
|
|
|
}, [handleOpenSettings]);
|
|
|
|
|
|
2026-03-09 13:02:11 -07:00
|
|
|
return (
|
|
|
|
|
<SidebarProvider className="h-dvh overflow-hidden overscroll-none">
|
2026-03-11 10:01:37 -07:00
|
|
|
<SidebarTrigger className="fixed left-[80px] top-[9px] z-50 h-6 w-6" />
|
2026-03-09 14:49:50 -07:00
|
|
|
<AppSidebar
|
|
|
|
|
channels={channels}
|
|
|
|
|
errorMessage={
|
|
|
|
|
channelsQuery.error instanceof Error
|
|
|
|
|
? channelsQuery.error.message
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
2026-03-09 19:32:56 -07:00
|
|
|
homeUrgentCount={homeUrgentCount}
|
2026-03-09 14:49:50 -07:00
|
|
|
isLoading={channelsQuery.isLoading}
|
2026-03-09 17:33:13 -07:00
|
|
|
isCreatingChannel={createChannelMutation.isPending}
|
|
|
|
|
onCreateChannel={async ({ description, name }) => {
|
|
|
|
|
const createdChannel = await createChannelMutation.mutateAsync({
|
|
|
|
|
name,
|
|
|
|
|
description,
|
|
|
|
|
channelType: "stream",
|
|
|
|
|
visibility: "open",
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-09 19:32:56 -07:00
|
|
|
React.startTransition(() => {
|
|
|
|
|
setSelectedChannelId(createdChannel.id);
|
|
|
|
|
setSelectedView("channel");
|
|
|
|
|
});
|
|
|
|
|
}}
|
2026-03-10 13:54:25 -07:00
|
|
|
onOpenSearch={() => {
|
|
|
|
|
setIsSearchOpen(true);
|
|
|
|
|
}}
|
2026-03-09 19:32:56 -07:00
|
|
|
onSelectHome={() => {
|
|
|
|
|
React.startTransition(() => {
|
|
|
|
|
setSelectedView("home");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
void homeFeedQuery.refetch();
|
2026-03-09 17:33:13 -07:00
|
|
|
}}
|
2026-03-10 13:54:25 -07:00
|
|
|
onSelectChannel={handleOpenChannel}
|
2026-03-11 10:34:41 -07:00
|
|
|
onSelectSettings={handleOpenSettings}
|
2026-03-09 14:49:50 -07:00
|
|
|
selectedChannelId={selectedChannel?.id ?? null}
|
2026-03-09 19:32:56 -07:00
|
|
|
selectedView={selectedView}
|
2026-03-11 11:11:06 -07:00
|
|
|
unreadChannelIds={unreadChannelIds}
|
2026-03-09 14:49:50 -07:00
|
|
|
/>
|
2026-03-09 13:02:11 -07:00
|
|
|
|
2026-03-10 09:46:08 -07:00
|
|
|
<SidebarInset
|
2026-03-11 10:01:37 -07:00
|
|
|
className="relative min-h-0 min-w-0 overflow-hidden pt-7"
|
2026-03-10 09:46:08 -07:00
|
|
|
key={contentPaneKey}
|
|
|
|
|
>
|
2026-03-11 10:01:37 -07:00
|
|
|
{/* Drag strip covering the traffic-light inset area */}
|
|
|
|
|
<div
|
|
|
|
|
className="absolute inset-x-0 top-0 flex h-7 items-center px-2"
|
|
|
|
|
onPointerDown={(e) => {
|
|
|
|
|
if (e.button !== 0) return;
|
|
|
|
|
const target = e.target as HTMLElement;
|
|
|
|
|
if (target.closest('button, a, input, [role="button"]')) return;
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
getCurrentWindow().startDragging();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2026-03-09 19:32:56 -07:00
|
|
|
{selectedView === "home" ? (
|
|
|
|
|
<ChatHeader
|
|
|
|
|
description="Personalized feed for mentions, reminders, channel activity, and agent work."
|
|
|
|
|
mode="home"
|
|
|
|
|
title="Home"
|
|
|
|
|
/>
|
2026-03-11 10:34:41 -07:00
|
|
|
) : selectedView === "settings" ? (
|
|
|
|
|
<ChatHeader
|
|
|
|
|
description="Theme, appearance, and profile preferences for your current identity."
|
|
|
|
|
mode="settings"
|
|
|
|
|
title="Settings"
|
|
|
|
|
/>
|
2026-03-09 19:32:56 -07:00
|
|
|
) : (
|
|
|
|
|
<ChatHeader
|
2026-03-10 17:11:49 -07:00
|
|
|
actions={
|
|
|
|
|
activeChannel ? (
|
|
|
|
|
<Button
|
|
|
|
|
data-testid="channel-management-trigger"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setIsChannelManagementOpen(true);
|
|
|
|
|
}}
|
|
|
|
|
size="sm"
|
|
|
|
|
type="button"
|
|
|
|
|
variant="outline"
|
|
|
|
|
>
|
|
|
|
|
<Settings2 className="h-4 w-4" />
|
|
|
|
|
Manage
|
|
|
|
|
</Button>
|
|
|
|
|
) : null
|
|
|
|
|
}
|
2026-03-09 19:32:56 -07:00
|
|
|
channelType={activeChannel?.channelType}
|
|
|
|
|
description={channelDescription}
|
|
|
|
|
title={activeChannel?.name ?? "Channels"}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-03-09 13:02:11 -07:00
|
|
|
|
|
|
|
|
<div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
2026-03-09 19:32:56 -07:00
|
|
|
{selectedView === "home" ? (
|
|
|
|
|
<HomeView
|
|
|
|
|
availableChannelIds={availableChannelIds}
|
|
|
|
|
currentPubkey={identityQuery.data?.pubkey}
|
|
|
|
|
errorMessage={
|
|
|
|
|
homeFeedQuery.error instanceof Error
|
|
|
|
|
? homeFeedQuery.error.message
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
feed={homeFeedQuery.data}
|
|
|
|
|
isLoading={homeFeedQuery.isLoading}
|
|
|
|
|
isRefreshing={homeFeedQuery.isRefetching}
|
2026-03-10 13:54:25 -07:00
|
|
|
onOpenChannel={handleOpenChannel}
|
2026-03-09 19:32:56 -07:00
|
|
|
onRefresh={() => {
|
|
|
|
|
void homeFeedQuery.refetch();
|
|
|
|
|
}}
|
|
|
|
|
/>
|
2026-03-11 10:34:41 -07:00
|
|
|
) : selectedView === "settings" ? (
|
|
|
|
|
<SettingsView
|
|
|
|
|
currentPubkey={identityQuery.data?.pubkey}
|
|
|
|
|
fallbackDisplayName={identityQuery.data?.displayName}
|
|
|
|
|
/>
|
2026-03-09 19:32:56 -07:00
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<MessageTimeline
|
|
|
|
|
emptyDescription={
|
|
|
|
|
activeChannel?.channelType === "forum"
|
|
|
|
|
? "Select a stream or DM to load real message history in this first integration pass."
|
|
|
|
|
: "Messages will appear here once the relay has history for this channel."
|
|
|
|
|
}
|
|
|
|
|
emptyTitle={
|
|
|
|
|
activeChannel
|
|
|
|
|
? activeChannel.channelType === "forum"
|
|
|
|
|
? "Forum channels are next"
|
|
|
|
|
: "No messages yet"
|
|
|
|
|
: "No channel selected"
|
|
|
|
|
}
|
2026-03-10 13:54:25 -07:00
|
|
|
isLoading={isTimelineLoading}
|
|
|
|
|
key={activeChannel?.id ?? "no-channel"}
|
2026-03-09 19:32:56 -07:00
|
|
|
messages={timelineMessages}
|
2026-03-10 13:54:25 -07:00
|
|
|
onTargetReached={(messageId) => {
|
|
|
|
|
setSearchAnchor((current) =>
|
|
|
|
|
current?.eventId === messageId ? null : current,
|
|
|
|
|
);
|
|
|
|
|
}}
|
|
|
|
|
targetMessageId={
|
|
|
|
|
activeChannel && searchAnchor?.channelId === activeChannel.id
|
|
|
|
|
? searchAnchor.eventId
|
|
|
|
|
: null
|
|
|
|
|
}
|
2026-03-09 19:32:56 -07:00
|
|
|
/>
|
|
|
|
|
<MessageComposer
|
|
|
|
|
channelName={activeChannel?.name ?? "channel"}
|
|
|
|
|
disabled={
|
|
|
|
|
!activeChannel ||
|
2026-03-10 17:11:49 -07:00
|
|
|
activeChannel.archivedAt !== null ||
|
2026-03-09 19:32:56 -07:00
|
|
|
activeChannel.channelType === "forum" ||
|
|
|
|
|
sendMessageMutation.isPending
|
|
|
|
|
}
|
|
|
|
|
isSending={sendMessageMutation.isPending}
|
2026-03-10 13:54:25 -07:00
|
|
|
key={activeChannel?.id ?? "no-channel"}
|
2026-03-09 19:32:56 -07:00
|
|
|
onSend={async (content) => {
|
|
|
|
|
await sendMessageMutation.mutateAsync(content);
|
|
|
|
|
}}
|
|
|
|
|
placeholder={
|
2026-03-10 17:11:49 -07:00
|
|
|
activeChannel?.archivedAt
|
|
|
|
|
? "Archived channels are read-only."
|
|
|
|
|
: activeChannel?.channelType === "forum"
|
|
|
|
|
? "Forum posting is not wired in this pass."
|
|
|
|
|
: activeChannel
|
|
|
|
|
? `Message #${activeChannel.name}`
|
|
|
|
|
: "Select a channel"
|
2026-03-09 19:32:56 -07:00
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
2026-03-09 13:02:11 -07:00
|
|
|
</div>
|
2026-03-10 13:54:25 -07:00
|
|
|
|
|
|
|
|
<SearchDialog
|
|
|
|
|
channels={channels}
|
2026-03-11 12:09:09 -07:00
|
|
|
currentPubkey={identityQuery.data?.pubkey}
|
2026-03-10 13:54:25 -07:00
|
|
|
onOpenResult={handleOpenSearchResult}
|
|
|
|
|
onOpenChange={setIsSearchOpen}
|
|
|
|
|
open={isSearchOpen}
|
|
|
|
|
/>
|
2026-03-10 17:11:49 -07:00
|
|
|
|
|
|
|
|
<ChannelManagementSheet
|
|
|
|
|
channel={activeChannel}
|
|
|
|
|
currentPubkey={identityQuery.data?.pubkey}
|
|
|
|
|
onDeleted={() => {
|
|
|
|
|
React.startTransition(() => {
|
|
|
|
|
setIsChannelManagementOpen(false);
|
|
|
|
|
setSelectedView("home");
|
|
|
|
|
});
|
|
|
|
|
}}
|
|
|
|
|
onOpenChange={setIsChannelManagementOpen}
|
|
|
|
|
open={isChannelManagementOpen && activeChannel !== null}
|
|
|
|
|
/>
|
2026-03-09 13:02:11 -07:00
|
|
|
</SidebarInset>
|
|
|
|
|
</SidebarProvider>
|
|
|
|
|
);
|
|
|
|
|
}
|