2026-03-09 14:49:50 -07:00
|
|
|
import * as React from "react";
|
|
|
|
|
|
2026-03-09 13:02:11 -07:00
|
|
|
import { ChatHeader } from "@/features/chat/ui/ChatHeader";
|
2026-03-09 14:49:50 -07:00
|
|
|
import {
|
|
|
|
|
useChannelsQuery,
|
|
|
|
|
useSelectedChannel,
|
|
|
|
|
} from "@/features/channels/hooks";
|
2026-03-09 13:02:11 -07:00
|
|
|
import { MessageComposer } from "@/features/chat/ui/MessageComposer";
|
|
|
|
|
import { MessageTimeline } from "@/features/chat/ui/MessageTimeline";
|
2026-03-09 14:49:50 -07:00
|
|
|
import {
|
|
|
|
|
useChannelMessagesQuery,
|
|
|
|
|
useChannelSubscription,
|
|
|
|
|
useSendMessageMutation,
|
|
|
|
|
} from "@/features/messages/hooks";
|
|
|
|
|
import { formatTimelineMessages } from "@/features/messages/lib/formatTimelineMessages";
|
2026-03-09 13:02:11 -07:00
|
|
|
import { AppSidebar } from "@/features/sidebar/ui/AppSidebar";
|
2026-03-09 14:49:50 -07:00
|
|
|
import { useIdentityQuery } from "@/shared/api/hooks";
|
2026-03-09 13:02:11 -07:00
|
|
|
import { SidebarInset, SidebarProvider } from "@/shared/ui/sidebar";
|
|
|
|
|
|
|
|
|
|
export function AppShell() {
|
2026-03-09 14:49:50 -07:00
|
|
|
const identityQuery = useIdentityQuery();
|
|
|
|
|
const channelsQuery = useChannelsQuery();
|
|
|
|
|
const channels = channelsQuery.data ?? [];
|
|
|
|
|
const { selectedChannel, setSelectedChannelId } = useSelectedChannel(
|
|
|
|
|
channels,
|
|
|
|
|
null,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const messagesQuery = useChannelMessagesQuery(selectedChannel);
|
|
|
|
|
useChannelSubscription(selectedChannel);
|
|
|
|
|
|
|
|
|
|
const sendMessageMutation = useSendMessageMutation(
|
|
|
|
|
selectedChannel,
|
|
|
|
|
identityQuery.data,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const timelineMessages = React.useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
formatTimelineMessages(
|
|
|
|
|
messagesQuery.data ?? [],
|
|
|
|
|
selectedChannel,
|
|
|
|
|
identityQuery.data?.pubkey,
|
|
|
|
|
),
|
|
|
|
|
[identityQuery.data?.pubkey, messagesQuery.data, selectedChannel],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const channelDescription = selectedChannel
|
|
|
|
|
? selectedChannel.channelType === "forum"
|
|
|
|
|
? `${selectedChannel.description} Forum channels are listed, but this first pass only wires message streams and DMs.`
|
|
|
|
|
: selectedChannel.description
|
|
|
|
|
: "Connect to the relay to browse channels and read messages.";
|
|
|
|
|
|
2026-03-09 13:02:11 -07:00
|
|
|
return (
|
|
|
|
|
<SidebarProvider className="h-dvh overflow-hidden overscroll-none">
|
2026-03-09 14:49:50 -07:00
|
|
|
<AppSidebar
|
|
|
|
|
channels={channels}
|
|
|
|
|
errorMessage={
|
|
|
|
|
channelsQuery.error instanceof Error
|
|
|
|
|
? channelsQuery.error.message
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
isLoading={channelsQuery.isLoading}
|
|
|
|
|
onSelectChannel={(channelId) => {
|
|
|
|
|
React.startTransition(() => setSelectedChannelId(channelId));
|
|
|
|
|
}}
|
|
|
|
|
selectedChannelId={selectedChannel?.id ?? null}
|
|
|
|
|
/>
|
2026-03-09 13:02:11 -07:00
|
|
|
|
|
|
|
|
<SidebarInset className="min-h-0 min-w-0 overflow-hidden">
|
|
|
|
|
<ChatHeader
|
2026-03-09 14:49:50 -07:00
|
|
|
channelType={selectedChannel?.channelType}
|
|
|
|
|
description={channelDescription}
|
|
|
|
|
title={selectedChannel?.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 14:49:50 -07:00
|
|
|
<MessageTimeline
|
|
|
|
|
emptyDescription={
|
|
|
|
|
selectedChannel?.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={
|
|
|
|
|
selectedChannel
|
|
|
|
|
? selectedChannel.channelType === "forum"
|
|
|
|
|
? "Forum channels are next"
|
|
|
|
|
: "No messages yet"
|
|
|
|
|
: "No channel selected"
|
|
|
|
|
}
|
|
|
|
|
isLoading={messagesQuery.isLoading}
|
|
|
|
|
messages={timelineMessages}
|
|
|
|
|
/>
|
|
|
|
|
<MessageComposer
|
|
|
|
|
channelName={selectedChannel?.name ?? "channel"}
|
|
|
|
|
disabled={
|
|
|
|
|
!selectedChannel ||
|
|
|
|
|
selectedChannel.channelType === "forum" ||
|
|
|
|
|
sendMessageMutation.isPending
|
|
|
|
|
}
|
|
|
|
|
isSending={sendMessageMutation.isPending}
|
|
|
|
|
key={selectedChannel?.id ?? "no-channel"}
|
|
|
|
|
onSend={async (content) => {
|
|
|
|
|
await sendMessageMutation.mutateAsync(content);
|
|
|
|
|
}}
|
|
|
|
|
placeholder={
|
|
|
|
|
selectedChannel?.channelType === "forum"
|
|
|
|
|
? "Forum posting is not wired in this pass."
|
|
|
|
|
: selectedChannel
|
|
|
|
|
? `Message #${selectedChannel.name}`
|
|
|
|
|
: "Select a channel"
|
|
|
|
|
}
|
|
|
|
|
/>
|
2026-03-09 13:02:11 -07:00
|
|
|
</div>
|
|
|
|
|
</SidebarInset>
|
|
|
|
|
</SidebarProvider>
|
|
|
|
|
);
|
|
|
|
|
}
|