Improve chat view
This commit is contained in:
parent
b7d5c08ac0
commit
df0bb73305
6 changed files with 152 additions and 111 deletions
|
|
@ -28,12 +28,7 @@ export const App = () => {
|
|||
<ThemeProvider>
|
||||
<SessionProvider>
|
||||
<Router>
|
||||
<div
|
||||
className={twMerge(
|
||||
"min-h-screen bg-gradient-to-br from-emerald-100 via-green-100 to-white",
|
||||
"dark:bg-gradient-to-br dark:from-[#0a1f0a] dark:via-[#051505] dark:to-black"
|
||||
)}
|
||||
>
|
||||
<div className={twMerge("min-h-screen bg-white", "dark:bg-white")}>
|
||||
<Routes>
|
||||
<Route path="/" element={<ProtectedRoute fallback="/login" />}>
|
||||
<Route
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function Layout({ children }: LayoutProps) {
|
|||
<SideNavigation isMobileMenuOpen={isMobileMenuOpen} />
|
||||
</div>
|
||||
|
||||
<main className="flex-1 overflow-auto p-4 md:p-6">{children}</main>
|
||||
<main className="flex-1 overflow-auto">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ export function MainNavigation({ isCollapsed }: { isCollapsed: boolean }) {
|
|||
},
|
||||
{
|
||||
path: "/chat",
|
||||
label: "Messages",
|
||||
label: "Discussions",
|
||||
icon: <MessageCircleIcon className="w-5 h-5" />,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
9
ui/src/main.css
Normal file
9
ui/src/main.css
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/* .str-chat {
|
||||
--str-chat__primary-color: #000;
|
||||
--str-chat__active-primary-color: #000;
|
||||
--str-chat__surface-color: #000;
|
||||
--str-chat__secondary-surface-color: #000;
|
||||
--str-chat__primary-surface-color: #000;
|
||||
--str-chat__primary-surface-color-low-emphasis: #000;
|
||||
--str-chat__border-radius-circle: 6px;
|
||||
} */
|
||||
|
|
@ -6,6 +6,7 @@ import { queryClient } from "./lib/api";
|
|||
import { GlobalToastRegion } from "./ui-library/toast/toast-region";
|
||||
|
||||
import "stream-chat-react/dist/css/v2/index.css";
|
||||
import "./main.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
|
|
|
|||
|
|
@ -1,134 +1,170 @@
|
|||
import { useState } from "react";
|
||||
import {
|
||||
Channel,
|
||||
ChannelList,
|
||||
MessageList,
|
||||
MessageInput,
|
||||
Window,
|
||||
Thread,
|
||||
ChannelPreviewUIComponentProps,
|
||||
useChatContext,
|
||||
SendButton,
|
||||
Channel,
|
||||
ChatView,
|
||||
Thread,
|
||||
ThreadList,
|
||||
ChannelPreviewUIComponentProps,
|
||||
} from "stream-chat-react";
|
||||
|
||||
export function ChatPage() {
|
||||
const { client, channel: activeChannel, setActiveChannel } = useChatContext();
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [showSidebar, setShowSidebar] = useState(true);
|
||||
interface ChannelData {
|
||||
name?: string;
|
||||
image?: string;
|
||||
members?: string[];
|
||||
}
|
||||
|
||||
// Custom Preview component for ChannelList
|
||||
const CustomChannelPreview = (props: ChannelPreviewUIComponentProps) => {
|
||||
const {
|
||||
channel,
|
||||
activeChannel: currentActiveChannel,
|
||||
setActiveChannel: setActive,
|
||||
} = props;
|
||||
const isSelected = channel?.id === currentActiveChannel?.id;
|
||||
const channelName =
|
||||
channel.data && "name" in channel.data ? channel.data.name : channel.id;
|
||||
const lastMessage = channel.state?.messages?.length
|
||||
? channel.state.messages[channel.state.messages.length - 1]?.text
|
||||
: undefined;
|
||||
return (
|
||||
<button
|
||||
className={`w-full text-left px-4 py-2 rounded transition-colors duration-100 hover:bg-blue-50 ${
|
||||
isSelected ? "bg-blue-100 font-bold" : ""
|
||||
}`}
|
||||
disabled={isSelected}
|
||||
onClick={() => {
|
||||
setActive?.(channel);
|
||||
setShowSidebar(false); // Hide sidebar on mobile when channel is selected
|
||||
}}
|
||||
>
|
||||
<div className="truncate">{String(channelName)}</div>
|
||||
<div className="text-xs text-gray-500 truncate">
|
||||
{lastMessage || "No messages yet"}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export function ChatPage() {
|
||||
const { client } = useChatContext();
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const handleCreateChannel = async () => {
|
||||
const name = window.prompt("Enter channel name:");
|
||||
if (!name) return;
|
||||
setCreating(true);
|
||||
setIsCreating(true);
|
||||
try {
|
||||
const newChannel = client.channel("messaging", name);
|
||||
const channelData: ChannelData = {
|
||||
name,
|
||||
members: [client.userID || ""],
|
||||
};
|
||||
const newChannel = client.channel("messaging", name, channelData);
|
||||
await newChannel.create();
|
||||
setActiveChannel(newChannel);
|
||||
setShowSidebar(false); // Hide sidebar on mobile after creating channel
|
||||
} catch {
|
||||
alert("Failed to create channel");
|
||||
} finally {
|
||||
setCreating(false);
|
||||
setIsCreating(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Responsive: show sidebar only if no channel is selected or on large screens
|
||||
return (
|
||||
<div className="flex h-screen w-screen bg-zinc-50 dark:bg-zinc-900">
|
||||
{/* Sidebar: hidden on mobile if a channel is selected */}
|
||||
<aside
|
||||
className={`w-72 h-full border-r border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 flex flex-col transition-transform duration-200 z-10
|
||||
${activeChannel && !showSidebar ? "hidden" : "block"} md:block`}
|
||||
>
|
||||
<div className="px-4 py-5 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between">
|
||||
<span className="text-lg font-semibold tracking-tight text-blue-700 dark:text-blue-300">
|
||||
Channels
|
||||
</span>
|
||||
<button
|
||||
className="ml-2 px-3 py-1.5 bg-blue-600 text-white rounded hover:bg-blue-700 transition disabled:opacity-50 text-sm"
|
||||
onClick={handleCreateChannel}
|
||||
disabled={creating}
|
||||
>
|
||||
{creating ? "..." : "+"}
|
||||
</button>
|
||||
const CustomChannelPreview = ({
|
||||
channel,
|
||||
}: ChannelPreviewUIComponentProps) => {
|
||||
const channelData = channel.data as ChannelData;
|
||||
return (
|
||||
<div className="px-4 py-3 hover:bg-gray-50 cursor-pointer">
|
||||
<div className="font-medium text-gray-900">
|
||||
{channelData?.name || channel.id || "Unnamed Channel"}
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<ChannelList
|
||||
Preview={(props) => (
|
||||
<CustomChannelPreview
|
||||
{...props}
|
||||
activeChannel={activeChannel}
|
||||
setActiveChannel={setActiveChannel}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<div className="text-sm text-gray-500 truncate">
|
||||
{channel.state.messages?.[channel.state.messages.length - 1]?.text ||
|
||||
"No messages yet"}
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
{/* Main Chat Area: takes full width if a channel is selected */}
|
||||
{activeChannel ? (
|
||||
<div className="h-full">
|
||||
<Channel channel={activeChannel} key={activeChannel.id}>
|
||||
<Window>
|
||||
{/* Header with back button on mobile */}
|
||||
<div className="border-b border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-950 flex items-center">
|
||||
<button
|
||||
className="md:hidden px-4 py-2 text-blue-600 hover:text-blue-800 focus:outline-none"
|
||||
onClick={() => setShowSidebar(true)}
|
||||
>
|
||||
← Back
|
||||
</button>
|
||||
<div className="px-6 py-4 text-lg font-semibold text-zinc-800 dark:text-zinc-100 truncate">
|
||||
{activeChannel.data &&
|
||||
typeof activeChannel.data === "object" &&
|
||||
"name" in activeChannel.data
|
||||
? (activeChannel.data as { name?: string }).name ||
|
||||
activeChannel.id
|
||||
: activeChannel.id}
|
||||
const actions = [
|
||||
"delete",
|
||||
"edit",
|
||||
"flag",
|
||||
"markUnread",
|
||||
"mute",
|
||||
"react",
|
||||
"reply",
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="h-screen w-screen bg-[#efeae2]">
|
||||
<ChatView>
|
||||
<ChatView.Selector />
|
||||
<div className="flex h-full overflow-hidden">
|
||||
<ChatView.Channels>
|
||||
<div className="flex h-full">
|
||||
<div className="flex h-full flex-col w-[360px] shrink-0 border-r border-gray-200 bg-amber-50 overflow-hidden">
|
||||
<div className="h-16 px-4 bg-[#008069] flex items-center justify-between">
|
||||
<span className="text-lg font-semibold tracking-tight text-white">
|
||||
Discussions
|
||||
</span>
|
||||
<button
|
||||
className="ml-2 p-2 text-white rounded-full hover:bg-[#0c977d] transition disabled:opacity-50 text-sm"
|
||||
onClick={handleCreateChannel}
|
||||
disabled={isCreating}
|
||||
>
|
||||
{isCreating ? "..." : "+"}
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto bg-white">
|
||||
<ChannelList Preview={CustomChannelPreview} />
|
||||
</div>
|
||||
</div>
|
||||
<MessageList />
|
||||
<MessageInput />
|
||||
<Thread />
|
||||
</Window>
|
||||
</Channel>
|
||||
<div className="flex-1 bg-zinc-100 w-full">
|
||||
<Channel SendButton={SendButton}>
|
||||
<Window>
|
||||
<div className="flex flex-col h-full w-full">
|
||||
<div className="h-16 px-4 bg-[#008069] flex items-center justify-between border-b border-gray-200">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="w-10 h-10 bg-white/20 rounded-full flex items-center justify-center">
|
||||
<span className="text-white font-semibold text-sm">
|
||||
#
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-white font-semibold text-lg">
|
||||
Channel Name
|
||||
</h2>
|
||||
<p className="text-white/70 text-sm">Active now</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button className="p-2 text-white/80 hover:text-white hover:bg-white/10 rounded-full transition">
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button className="p-2 text-white/80 hover:text-white hover:bg-white/10 rounded-full transition">
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col min-h-0 w-full">
|
||||
<MessageList messageActions={actions} />
|
||||
<MessageInput
|
||||
focus
|
||||
audioRecordingEnabled
|
||||
asyncMessagesMultiSendEnabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Window>
|
||||
</Channel>
|
||||
</div>
|
||||
</div>
|
||||
</ChatView.Channels>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 flex items-center justify-center text-zinc-400 text-lg select-none">
|
||||
Select a channel to start chatting
|
||||
</div>
|
||||
)}
|
||||
<ChatView.Threads>
|
||||
<ThreadList />
|
||||
<ChatView.ThreadAdapter>
|
||||
<Thread virtualized />
|
||||
</ChatView.ThreadAdapter>
|
||||
</ChatView.Threads>
|
||||
</ChatView>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue