feat(ui): update Header and ChannelSidebar components
Refine Header with improved layout, update ChannelSidebar with channel navigation and ChatPage integration.
This commit is contained in:
parent
c015871024
commit
31e9bb68ac
@ -10,6 +10,7 @@ import { useProjectInfo } from "@/hooks/useProjectInfo";
|
|||||||
import { useConversationQuery } from "@/hooks/useAiChatQuery";
|
import { useConversationQuery } from "@/hooks/useAiChatQuery";
|
||||||
import { CodePreviewPanel } from "@/components/chat/CodePreviewPanel";
|
import { CodePreviewPanel } from "@/components/chat/CodePreviewPanel";
|
||||||
import { CodePreviewProvider, type CodePreviewPayload } from "@/components/chat/CodePreviewContext";
|
import { CodePreviewProvider, type CodePreviewPayload } from "@/components/chat/CodePreviewContext";
|
||||||
|
import { ProjectJoinBanner, useProjectLayout } from "@/app/project/layout";
|
||||||
|
|
||||||
interface ChatPageProps {
|
interface ChatPageProps {
|
||||||
scope: "personal" | "project";
|
scope: "personal" | "project";
|
||||||
@ -27,6 +28,7 @@ export function ChatPage({ scope }: ChatPageProps) {
|
|||||||
const [userModel, setSelectedModel] = useState<SelectedModel | null>(null);
|
const [userModel, setSelectedModel] = useState<SelectedModel | null>(null);
|
||||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(true);
|
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(true);
|
||||||
const [activeCode, setActiveCode] = useState<CodePreviewPayload | null>(null);
|
const [activeCode, setActiveCode] = useState<CodePreviewPayload | null>(null);
|
||||||
|
const { isProjectPreview } = useProjectLayout();
|
||||||
|
|
||||||
const { data: conversation } = useConversationQuery(selectedConversationId || "");
|
const { data: conversation } = useConversationQuery(selectedConversationId || "");
|
||||||
|
|
||||||
@ -131,24 +133,36 @@ export function ChatPage({ scope }: ChatPageProps) {
|
|||||||
{selectedConversationId ? (
|
{selectedConversationId ? (
|
||||||
<>
|
<>
|
||||||
<ChatMessageList conversationId={selectedConversationId} setIsStreaming={setIsStreaming} />
|
<ChatMessageList conversationId={selectedConversationId} setIsStreaming={setIsStreaming} />
|
||||||
<ChatMessageInput
|
{scope === "project" && isProjectPreview ? (
|
||||||
conversationId={selectedConversationId}
|
<div className="shrink-0 px-4 pb-4">
|
||||||
isStreaming={isStreaming}
|
<div className="mx-auto max-w-3xl">
|
||||||
setIsStreaming={setIsStreaming}
|
<ProjectJoinBanner compact message="Join this project to start project chat." />
|
||||||
onSelectConversation={handleSelectConversation}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
) : (
|
||||||
|
<ChatMessageInput
|
||||||
|
conversationId={selectedConversationId}
|
||||||
|
isStreaming={isStreaming}
|
||||||
|
setIsStreaming={setIsStreaming}
|
||||||
|
onSelectConversation={handleSelectConversation}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex-1 flex flex-col items-center justify-center px-4 gap-4">
|
<div className="flex-1 flex flex-col items-center justify-center px-4 gap-4">
|
||||||
<div className="w-full max-w-3xl">
|
<div className="w-full max-w-3xl">
|
||||||
<ChatMessageList conversationId={null} setIsStreaming={setIsStreaming} />
|
<ChatMessageList conversationId={null} setIsStreaming={setIsStreaming} />
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<ChatMessageInput
|
{scope === "project" && isProjectPreview ? (
|
||||||
conversationId={null}
|
<ProjectJoinBanner compact message="Join this project to start project chat." />
|
||||||
isStreaming={isStreaming}
|
) : (
|
||||||
setIsStreaming={setIsStreaming}
|
<ChatMessageInput
|
||||||
onSelectConversation={handleSelectConversation}
|
conversationId={null}
|
||||||
/>
|
isStreaming={isStreaming}
|
||||||
|
setIsStreaming={setIsStreaming}
|
||||||
|
onSelectConversation={handleSelectConversation}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -41,8 +41,9 @@ interface ChannelSidebarProps {
|
|||||||
export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: ChannelSidebarProps) {
|
export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: ChannelSidebarProps) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const {projectName} = useParams<{ projectName: string }>();
|
const {projectName} = useParams<{ projectName: string }>();
|
||||||
const {data: roomsData, isLoading} = useRoomsQuery(projectName);
|
|
||||||
const {data: projectInfo} = useProjectInfo(projectName);
|
const {data: projectInfo} = useProjectInfo(projectName);
|
||||||
|
const isProjectMember = !!projectInfo?.role;
|
||||||
|
const {data: roomsData, isLoading} = useRoomsQuery(isProjectMember ? projectName : undefined);
|
||||||
const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false);
|
const [isCreateMenuOpen, setIsCreateMenuOpen] = useState(false);
|
||||||
|
|
||||||
const rooms = useMemo(() => roomsData?.rooms ?? [], [roomsData?.rooms]);
|
const rooms = useMemo(() => roomsData?.rooms ?? [], [roomsData?.rooms]);
|
||||||
@ -103,14 +104,16 @@ export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: Channel
|
|||||||
>
|
>
|
||||||
<Search className="w-[14px] h-[14px]"/>
|
<Search className="w-[14px] h-[14px]"/>
|
||||||
</button>
|
</button>
|
||||||
<button
|
{isProjectMember && (
|
||||||
onClick={() => setIsCreateMenuOpen(true)}
|
<button
|
||||||
className={CHANNEL_SIDEBAR.iconButton}
|
onClick={() => setIsCreateMenuOpen(true)}
|
||||||
style={{color: "var(--text-secondary)"}}
|
className={CHANNEL_SIDEBAR.iconButton}
|
||||||
title="Create new..."
|
style={{color: "var(--text-secondary)"}}
|
||||||
>
|
title="Create new..."
|
||||||
<Plus className="w-[14px] h-[14px]"/>
|
>
|
||||||
</button>
|
<Plus className="w-[14px] h-[14px]"/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{onCollapse && (
|
{onCollapse && (
|
||||||
<button
|
<button
|
||||||
onClick={onCollapse}
|
onClick={onCollapse}
|
||||||
@ -178,7 +181,7 @@ export const ChannelSidebar = memo(function ChannelSidebar({onCollapse}: Channel
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{!isProjectMember ? null : isLoading ? (
|
||||||
<div className="px-4 py-2 text-[var(--text-muted)]">Loading channels...</div>
|
<div className="px-4 py-2 text-[var(--text-muted)]">Loading channels...</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -39,6 +39,7 @@ const ME_NAV_SIBLINGS: BreadcrumbSibling[] = [
|
|||||||
{ label: "Stars", path: "/me/stars" },
|
{ label: "Stars", path: "/me/stars" },
|
||||||
{ label: "Following", path: "/me/following" },
|
{ label: "Following", path: "/me/following" },
|
||||||
{ label: "Followers", path: "/me/followers" },
|
{ label: "Followers", path: "/me/followers" },
|
||||||
|
{ label: "Invitations", path: "/me/invitations" },
|
||||||
];
|
];
|
||||||
|
|
||||||
function getProjectNavSiblings(projectName: string): BreadcrumbSibling[] {
|
function getProjectNavSiblings(projectName: string): BreadcrumbSibling[] {
|
||||||
@ -167,7 +168,7 @@ const TOOLBAR_ICONS = [
|
|||||||
export const Header = memo(function Header() {
|
export const Header = memo(function Header() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { segments, projects } = useBreadcrumbs();
|
const { segments, projects } = useBreadcrumbs();
|
||||||
const { showMembers, setShowMembers } = useProjectLayout();
|
const { isProjectMember, showMembers, setShowMembers } = useProjectLayout();
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const roomContext = useOptionalRoom();
|
const roomContext = useOptionalRoom();
|
||||||
|
|
||||||
@ -254,7 +255,7 @@ export const Header = memo(function Header() {
|
|||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
{location.pathname.startsWith("/me") ? null : (
|
{location.pathname.startsWith("/me") ? null : (
|
||||||
<>
|
<>
|
||||||
{roomContext?.currentRoom && location.pathname.includes("/channel/") && (
|
{isProjectMember && roomContext?.currentRoom && location.pathname.includes("/channel/") && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowSettings(true)}
|
onClick={() => setShowSettings(true)}
|
||||||
className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors hover:bg-hover-bg"
|
className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors hover:bg-hover-bg"
|
||||||
@ -264,32 +265,34 @@ export const Header = memo(function Header() {
|
|||||||
<Settings className="w-[18px] h-[18px]" />
|
<Settings className="w-[18px] h-[18px]" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
{isProjectMember && (
|
||||||
onClick={() => setShowMembers(!showMembers)}
|
<button
|
||||||
className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors"
|
onClick={() => setShowMembers(!showMembers)}
|
||||||
style={
|
className="w-8 h-8 flex items-center justify-center rounded-[4px] transition-colors"
|
||||||
showMembers
|
style={
|
||||||
? {
|
showMembers
|
||||||
color: "var(--text-primary)",
|
? {
|
||||||
backgroundColor: "var(--hover-bg-strong)",
|
color: "var(--text-primary)",
|
||||||
}
|
backgroundColor: "var(--hover-bg-strong)",
|
||||||
: { color: "var(--text-secondary)" }
|
}
|
||||||
}
|
: { color: "var(--text-secondary)" }
|
||||||
>
|
}
|
||||||
<svg
|
|
||||||
className="w-[18px] h-[18px]"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
>
|
||||||
<path
|
<svg
|
||||||
strokeLinecap="round"
|
className="w-[18px] h-[18px]"
|
||||||
strokeLinejoin="round"
|
fill="none"
|
||||||
strokeWidth={1.5}
|
stroke="currentColor"
|
||||||
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"
|
viewBox="0 0 24 24"
|
||||||
/>
|
>
|
||||||
</svg>
|
<path
|
||||||
</button>
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{TOOLBAR_ICONS.map((icon, i) => (
|
{TOOLBAR_ICONS.map((icon, i) => (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user