refactor: enhance ConnectToolsBanner functionality and update sidebar navigation handling

This commit is contained in:
Anish Sarkar 2026-05-17 16:46:34 +05:30
parent a49ee05456
commit 88a43cdd65
3 changed files with 94 additions and 66 deletions

View file

@ -261,7 +261,10 @@ const BANNER_CONNECTORS = [
const BANNER_DISMISSED_KEY = "surfsense-connect-tools-banner-dismissed"; const BANNER_DISMISSED_KEY = "surfsense-connect-tools-banner-dismissed";
const ConnectToolsBanner: FC<{ isThreadEmpty: boolean }> = ({ isThreadEmpty }) => { const ConnectToolsBanner: FC<{
isThreadEmpty: boolean;
onVisibleChange?: (visible: boolean) => void;
}> = ({ isThreadEmpty, onVisibleChange }) => {
const { data: connectors } = useAtomValue(connectorsAtom); const { data: connectors } = useAtomValue(connectorsAtom);
const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom); const setConnectorDialogOpen = useSetAtom(connectorDialogOpenAtom);
const [dismissed, setDismissed] = useState(() => { const [dismissed, setDismissed] = useState(() => {
@ -270,8 +273,13 @@ const ConnectToolsBanner: FC<{ isThreadEmpty: boolean }> = ({ isThreadEmpty }) =
}); });
const hasConnectors = (connectors?.length ?? 0) > 0; const hasConnectors = (connectors?.length ?? 0) > 0;
const isVisible = !dismissed && !hasConnectors && isThreadEmpty;
if (dismissed || hasConnectors || !isThreadEmpty) return null; useEffect(() => {
onVisibleChange?.(isVisible);
}, [isVisible, onVisibleChange]);
if (!isVisible) return null;
const handleDismiss = (e: React.MouseEvent) => { const handleDismiss = (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
@ -280,42 +288,41 @@ const ConnectToolsBanner: FC<{ isThreadEmpty: boolean }> = ({ isThreadEmpty }) =
}; };
return ( return (
<div className="border-t border-border/50"> <div className="relative z-0 -mt-5 flex min-w-0 items-center gap-2 rounded-b-3xl border border-input bg-muted/40 px-4 pt-7 pb-3 shadow-sm shadow-black/5 dark:shadow-black/10">
<div className="flex w-full items-center gap-2.5 px-4 py-2.5"> <Button
<Button type="button"
type="button" variant="ghost"
variant="ghost" size="sm"
size="sm" className="h-7 min-w-0 cursor-pointer justify-start gap-2 rounded-md px-0 text-[13px] font-normal text-muted-foreground select-none hover:bg-transparent hover:text-foreground"
className="h-auto flex-1 justify-start gap-2.5 px-0 py-0 text-left cursor-pointer select-none hover:bg-transparent" onClick={() => setConnectorDialogOpen(true)}
onClick={() => setConnectorDialogOpen(true)} >
> <Unplug className="size-4 shrink-0" />
<Unplug className="size-4 text-muted-foreground shrink-0" /> <span className="truncate">Connect your tools</span>
<span className="text-[13px] text-muted-foreground/80 flex-1">Connect your tools</span> </Button>
<AvatarGroup className="shrink-0"> <div className="min-w-0 flex-1" />
{BANNER_CONNECTORS.map(({ type }, i) => ( <AvatarGroup className="shrink-0">
<Avatar {BANNER_CONNECTORS.map(({ type }, i) => (
key={type} <Avatar
className="size-6" key={type}
style={{ zIndex: BANNER_CONNECTORS.length - i }} className="size-5"
> style={{ zIndex: BANNER_CONNECTORS.length - i }}
<AvatarFallback className="bg-muted text-[10px]"> >
{getConnectorIcon(type, "size-3.5")} <AvatarFallback className="bg-accent text-[10px]">
</AvatarFallback> {getConnectorIcon(type, "size-3")}
</Avatar> </AvatarFallback>
))} </Avatar>
</AvatarGroup> ))}
</Button> </AvatarGroup>
<Button <Button
type="button" type="button"
onClick={handleDismiss} onClick={handleDismiss}
variant="ghost" variant="ghost"
size="icon" size="icon"
className="size-auto shrink-0 ml-0.5 -mr-1 p-1.5 text-muted-foreground/40 hover:bg-transparent hover:text-accent-foreground cursor-pointer" className="size-7 shrink-0 cursor-pointer rounded-md text-muted-foreground hover:bg-transparent hover:text-foreground"
aria-label="Dismiss" aria-label="Dismiss"
> >
<X className="size-3.5" /> <X className="size-3.5" />
</Button> </Button>
</div>
</div> </div>
); );
}; };
@ -426,6 +433,7 @@ const Composer: FC = () => {
const isThreadEmpty = useAuiState(({ thread }) => thread.isEmpty); const isThreadEmpty = useAuiState(({ thread }) => thread.isEmpty);
const isThreadRunning = useAuiState(({ thread }) => thread.isRunning); const isThreadRunning = useAuiState(({ thread }) => thread.isRunning);
const [connectToolsTrayVisible, setConnectToolsTrayVisible] = useState(false);
const currentPlaceholder = COMPOSER_PLACEHOLDER; const currentPlaceholder = COMPOSER_PLACEHOLDER;
@ -735,32 +743,42 @@ const Composer: FC = () => {
/> />
</div> </div>
)} )}
<div className="aui-composer-attachment-dropzone flex w-full flex-col overflow-hidden rounded-3xl border-input bg-muted pt-2 shadow-sm shadow-black/5 outline-none transition-shadow dark:shadow-black/10"> <div className="flex w-full flex-col">
<PendingScreenImageStrip /> <div
{clipboardInitialText && ( className={cn(
<ClipboardChip "aui-composer-attachment-dropzone relative z-10 flex w-full flex-col overflow-hidden rounded-3xl border border-input bg-muted pt-2 shadow-sm shadow-black/5 outline-none transition-shadow dark:shadow-black/10",
text={clipboardInitialText} connectToolsTrayVisible && "rounded-b-3xl shadow-none dark:shadow-none"
onDismiss={() => setClipboardInitialText(undefined)} )}
/> >
)} <PendingScreenImageStrip />
<div className="aui-composer-input-wrapper px-4 pt-3 pb-6"> {clipboardInitialText && (
<InlineMentionEditor <ClipboardChip
ref={editorRef} text={clipboardInitialText}
placeholder={currentPlaceholder} onDismiss={() => setClipboardInitialText(undefined)}
onMentionTrigger={handleMentionTrigger} />
onMentionClose={handleMentionClose} )}
onActionTrigger={handleActionTrigger} <div className="aui-composer-input-wrapper px-4 pt-3 pb-6">
onActionClose={handleActionClose} <InlineMentionEditor
onChange={handleEditorChange} ref={editorRef}
onDocumentRemove={handleDocumentRemove} placeholder={currentPlaceholder}
onSubmit={handleSubmit} onMentionTrigger={handleMentionTrigger}
onKeyDown={handleKeyDown} onMentionClose={handleMentionClose}
className="min-h-[24px]" onActionTrigger={handleActionTrigger}
/> onActionClose={handleActionClose}
onChange={handleEditorChange}
onDocumentRemove={handleDocumentRemove}
onSubmit={handleSubmit}
onKeyDown={handleKeyDown}
className="min-h-[24px]"
/>
</div>
<ComposerAction isBlockedByOtherUser={isBlockedByOtherUser} />
<ConnectorIndicator showTrigger={false} />
</div> </div>
<ComposerAction isBlockedByOtherUser={isBlockedByOtherUser} /> <ConnectToolsBanner
<ConnectorIndicator showTrigger={false} /> isThreadEmpty={isThreadEmpty}
<ConnectToolsBanner isThreadEmpty={isThreadEmpty} /> onVisibleChange={setConnectToolsTrayVisible}
/>
</div> </div>
</ComposerPrimitive.Root> </ComposerPrimitive.Root>
); );

View file

@ -210,6 +210,7 @@ export function MobileSidebar({
} }
: undefined : undefined
} }
onNavigate={() => onOpenChange(false)}
announcementUnreadCount={announcementUnreadCount} announcementUnreadCount={announcementUnreadCount}
onLogout={onLogout} onLogout={onLogout}
pageUsage={pageUsage} pageUsage={pageUsage}

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { CreditCard, SquarePen, Zap } from "lucide-react"; import { CreditCard, Dot, SquarePen, Zap } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@ -83,6 +83,7 @@ interface SidebarProps {
onManageMembers?: () => void; onManageMembers?: () => void;
onUserSettings?: () => void; onUserSettings?: () => void;
onAnnouncements?: () => void; onAnnouncements?: () => void;
onNavigate?: () => void;
announcementUnreadCount?: number; announcementUnreadCount?: number;
onLogout?: () => void; onLogout?: () => void;
pageUsage?: PageUsage; pageUsage?: PageUsage;
@ -119,6 +120,7 @@ export function Sidebar({
onManageMembers, onManageMembers,
onUserSettings, onUserSettings,
onAnnouncements, onAnnouncements,
onNavigate,
announcementUnreadCount = 0, announcementUnreadCount = 0,
onLogout, onLogout,
pageUsage, pageUsage,
@ -344,6 +346,7 @@ export function Sidebar({
pageUsage={pageUsage} pageUsage={pageUsage}
isCollapsed={isCollapsed} isCollapsed={isCollapsed}
hasNavSectionAbove={footerNavItems.length > 0} hasNavSectionAbove={footerNavItems.length > 0}
onNavigate={onNavigate}
/> />
{renderUserProfile && ( {renderUserProfile && (
@ -367,10 +370,12 @@ function SidebarUsageFooter({
pageUsage, pageUsage,
isCollapsed, isCollapsed,
hasNavSectionAbove = false, hasNavSectionAbove = false,
onNavigate,
}: { }: {
pageUsage?: PageUsage; pageUsage?: PageUsage;
isCollapsed: boolean; isCollapsed: boolean;
hasNavSectionAbove?: boolean; hasNavSectionAbove?: boolean;
onNavigate?: () => void;
}) { }) {
const params = useParams(); const params = useParams();
const searchSpaceId = params?.search_space_id ?? ""; const searchSpaceId = params?.search_space_id ?? "";
@ -424,6 +429,7 @@ function SidebarUsageFooter({
<div className="space-y-0.5"> <div className="space-y-0.5">
<Link <Link
href={`/dashboard/${searchSpaceId}/more-pages`} href={`/dashboard/${searchSpaceId}/more-pages`}
onClick={onNavigate}
className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 transition-colors hover:bg-accent" className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 transition-colors hover:bg-accent"
> >
<span className="flex items-center gap-1.5 text-xs text-muted-foreground group-hover:text-accent-foreground"> <span className="flex items-center gap-1.5 text-xs text-muted-foreground group-hover:text-accent-foreground">
@ -436,14 +442,17 @@ function SidebarUsageFooter({
</Link> </Link>
<Link <Link
href={`/dashboard/${searchSpaceId}/buy-more`} href={`/dashboard/${searchSpaceId}/buy-more`}
onClick={onNavigate}
className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 transition-colors hover:bg-accent" className="group flex w-full items-center justify-between rounded-md px-1.5 py-1 transition-colors hover:bg-accent"
> >
<span className="flex items-center gap-1.5 text-xs text-muted-foreground group-hover:text-accent-foreground"> <span className="flex items-center gap-1.5 text-xs text-muted-foreground group-hover:text-accent-foreground">
<CreditCard className="h-3 w-3 shrink-0" /> <CreditCard className="h-3 w-3 shrink-0" />
Buy More Buy More
</span> </span>
<span className="text-[10px] font-medium text-muted-foreground"> <span className="flex items-center text-[10px] font-medium text-muted-foreground">
$1/1k &middot; $1/1M $1/1k
<Dot className="h-3 w-3" />
$1/1M
</span> </span>
</Link> </Link>
</div> </div>