mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-01 20:03:30 +02:00
feat: implement search space deletion and fixed rback issues with shared chats
This commit is contained in:
parent
fbeffd58fe
commit
25b9118306
22 changed files with 671 additions and 144 deletions
|
|
@ -12,6 +12,8 @@ interface IconRailProps {
|
|||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onSearchSpaceDelete?: (searchSpace: SearchSpace) => void;
|
||||
onSearchSpaceSettings?: (searchSpace: SearchSpace) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
|
@ -20,6 +22,8 @@ export function IconRail({
|
|||
searchSpaces,
|
||||
activeSearchSpaceId,
|
||||
onSearchSpaceSelect,
|
||||
onSearchSpaceDelete,
|
||||
onSearchSpaceSettings,
|
||||
onAddSearchSpace,
|
||||
className,
|
||||
}: IconRailProps) {
|
||||
|
|
@ -32,7 +36,13 @@ export function IconRail({
|
|||
key={searchSpace.id}
|
||||
name={searchSpace.name}
|
||||
isActive={searchSpace.id === activeSearchSpaceId}
|
||||
isShared={searchSpace.memberCount > 1}
|
||||
isOwner={searchSpace.isOwner}
|
||||
onClick={() => onSearchSpaceSelect(searchSpace.id)}
|
||||
onDelete={onSearchSpaceDelete ? () => onSearchSpaceDelete(searchSpace) : undefined}
|
||||
onSettings={
|
||||
onSearchSpaceSettings ? () => onSearchSpaceSettings(searchSpace) : undefined
|
||||
}
|
||||
size="md"
|
||||
/>
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { Settings, Trash2, Users } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuTrigger,
|
||||
} from "@/components/ui/context-menu";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SearchSpaceAvatarProps {
|
||||
name: string;
|
||||
isActive?: boolean;
|
||||
isShared?: boolean;
|
||||
isOwner?: boolean;
|
||||
onClick?: () => void;
|
||||
onDelete?: () => void;
|
||||
onSettings?: () => void;
|
||||
size?: "sm" | "md";
|
||||
}
|
||||
|
||||
|
|
@ -45,32 +58,103 @@ function getInitials(name: string): string {
|
|||
export function SearchSpaceAvatar({
|
||||
name,
|
||||
isActive,
|
||||
isShared,
|
||||
isOwner = true,
|
||||
onClick,
|
||||
onDelete,
|
||||
onSettings,
|
||||
size = "md",
|
||||
}: SearchSpaceAvatarProps) {
|
||||
const t = useTranslations("searchSpace");
|
||||
const tCommon = useTranslations("common");
|
||||
const bgColor = stringToColor(name);
|
||||
const initials = getInitials(name);
|
||||
const sizeClasses = size === "sm" ? "h-8 w-8 text-xs" : "h-10 w-10 text-sm";
|
||||
|
||||
const tooltipContent = (
|
||||
<div className="flex flex-col">
|
||||
<span>{name}</span>
|
||||
{isShared && (
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{isOwner ? tCommon("owner") : tCommon("shared")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const avatarButton = (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"relative flex items-center justify-center rounded-lg font-semibold text-white transition-all",
|
||||
"hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
sizeClasses,
|
||||
isActive && "ring-2 ring-primary ring-offset-1 ring-offset-background"
|
||||
)}
|
||||
style={{ backgroundColor: bgColor }}
|
||||
>
|
||||
{initials}
|
||||
{/* Shared indicator badge */}
|
||||
{isShared && (
|
||||
<span
|
||||
className={cn(
|
||||
"absolute -top-1 -right-1 flex items-center justify-center rounded-full bg-blue-500 text-white shadow-sm",
|
||||
size === "sm" ? "h-3.5 w-3.5" : "h-4 w-4"
|
||||
)}
|
||||
title={tCommon("shared")}
|
||||
>
|
||||
<Users className={cn(size === "sm" ? "h-2 w-2" : "h-2.5 w-2.5")} />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
|
||||
// If delete or settings handlers are provided, wrap with context menu
|
||||
if (onDelete || onSettings) {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<ContextMenuTrigger asChild>
|
||||
<div className="inline-block">{avatarButton}</div>
|
||||
</ContextMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
{tooltipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<ContextMenuContent className="w-48">
|
||||
{onSettings && (
|
||||
<ContextMenuItem onClick={onSettings}>
|
||||
<Settings className="mr-2 h-4 w-4" />
|
||||
{tCommon("settings")}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{onSettings && onDelete && <ContextMenuSeparator />}
|
||||
{onDelete && isOwner && (
|
||||
<ContextMenuItem variant="destructive" onClick={onDelete}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{tCommon("delete")}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
{onDelete && !isOwner && (
|
||||
<ContextMenuItem variant="destructive" onClick={onDelete}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
{t("leave")}
|
||||
</ContextMenuItem>
|
||||
)}
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
// No context menu needed
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
"flex items-center justify-center rounded-lg font-semibold text-white transition-all",
|
||||
"hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
sizeClasses,
|
||||
isActive && "ring-2 ring-primary ring-offset-1 ring-offset-background"
|
||||
)}
|
||||
style={{ backgroundColor: bgColor }}
|
||||
>
|
||||
{initials}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipTrigger asChild>{avatarButton}</TooltipTrigger>
|
||||
<TooltipContent side="right" sideOffset={8}>
|
||||
{name}
|
||||
{tooltipContent}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ interface LayoutShellProps {
|
|||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onSearchSpaceDelete?: (searchSpace: SearchSpace) => void;
|
||||
onSearchSpaceSettings?: (searchSpace: SearchSpace) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
searchSpace: SearchSpace | null;
|
||||
navItems: NavItem[];
|
||||
|
|
@ -46,6 +48,8 @@ export function LayoutShell({
|
|||
searchSpaces,
|
||||
activeSearchSpaceId,
|
||||
onSearchSpaceSelect,
|
||||
onSearchSpaceDelete,
|
||||
onSearchSpaceSettings,
|
||||
onAddSearchSpace,
|
||||
searchSpace,
|
||||
navItems,
|
||||
|
|
@ -96,6 +100,8 @@ export function LayoutShell({
|
|||
searchSpaces={searchSpaces}
|
||||
activeSearchSpaceId={activeSearchSpaceId}
|
||||
onSearchSpaceSelect={onSearchSpaceSelect}
|
||||
onSearchSpaceDelete={onSearchSpaceDelete}
|
||||
onSearchSpaceSettings={onSearchSpaceSettings}
|
||||
onAddSearchSpace={onAddSearchSpace}
|
||||
searchSpace={searchSpace}
|
||||
navItems={navItems}
|
||||
|
|
@ -133,6 +139,8 @@ export function LayoutShell({
|
|||
searchSpaces={searchSpaces}
|
||||
activeSearchSpaceId={activeSearchSpaceId}
|
||||
onSearchSpaceSelect={onSearchSpaceSelect}
|
||||
onSearchSpaceDelete={onSearchSpaceDelete}
|
||||
onSearchSpaceSettings={onSearchSpaceSettings}
|
||||
onAddSearchSpace={onAddSearchSpace}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ interface MobileSidebarProps {
|
|||
searchSpaces: SearchSpace[];
|
||||
activeSearchSpaceId: number | null;
|
||||
onSearchSpaceSelect: (id: number) => void;
|
||||
onSearchSpaceDelete?: (searchSpace: SearchSpace) => void;
|
||||
onSearchSpaceSettings?: (searchSpace: SearchSpace) => void;
|
||||
onAddSearchSpace: () => void;
|
||||
searchSpace: SearchSpace | null;
|
||||
navItems: NavItem[];
|
||||
|
|
@ -48,6 +50,8 @@ export function MobileSidebar({
|
|||
searchSpaces,
|
||||
activeSearchSpaceId,
|
||||
onSearchSpaceSelect,
|
||||
onSearchSpaceDelete,
|
||||
onSearchSpaceSettings,
|
||||
onAddSearchSpace,
|
||||
searchSpace,
|
||||
navItems,
|
||||
|
|
@ -94,7 +98,13 @@ export function MobileSidebar({
|
|||
<SearchSpaceAvatar
|
||||
name={space.name}
|
||||
isActive={space.id === activeSearchSpaceId}
|
||||
isShared={space.memberCount > 1}
|
||||
isOwner={space.isOwner}
|
||||
onClick={() => handleSearchSpaceSelect(space.id)}
|
||||
onDelete={onSearchSpaceDelete ? () => onSearchSpaceDelete(space) : undefined}
|
||||
onSettings={
|
||||
onSearchSpaceSettings ? () => onSearchSpaceSettings(space) : undefined
|
||||
}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -111,33 +121,33 @@ export function MobileSidebar({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar Content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Sidebar
|
||||
searchSpace={searchSpace}
|
||||
isCollapsed={false}
|
||||
navItems={navItems}
|
||||
onNavItemClick={handleNavItemClick}
|
||||
chats={chats}
|
||||
sharedChats={sharedChats}
|
||||
activeChatId={activeChatId}
|
||||
onNewChat={() => {
|
||||
onNewChat();
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onChatSelect={handleChatSelect}
|
||||
onChatDelete={onChatDelete}
|
||||
onViewAllSharedChats={onViewAllSharedChats}
|
||||
onViewAllPrivateChats={onViewAllPrivateChats}
|
||||
user={user}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
onUserSettings={onUserSettings}
|
||||
onLogout={onLogout}
|
||||
pageUsage={pageUsage}
|
||||
className="w-full border-none"
|
||||
/>
|
||||
</div>
|
||||
{/* Sidebar Content */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Sidebar
|
||||
searchSpace={searchSpace}
|
||||
isCollapsed={false}
|
||||
navItems={navItems}
|
||||
onNavItemClick={handleNavItemClick}
|
||||
chats={chats}
|
||||
sharedChats={sharedChats}
|
||||
activeChatId={activeChatId}
|
||||
onNewChat={() => {
|
||||
onNewChat();
|
||||
onOpenChange(false);
|
||||
}}
|
||||
onChatSelect={handleChatSelect}
|
||||
onChatDelete={onChatDelete}
|
||||
onViewAllSharedChats={onViewAllSharedChats}
|
||||
onViewAllPrivateChats={onViewAllPrivateChats}
|
||||
user={user}
|
||||
onSettings={onSettings}
|
||||
onManageMembers={onManageMembers}
|
||||
onUserSettings={onUserSettings}
|
||||
onLogout={onLogout}
|
||||
pageUsage={pageUsage}
|
||||
className="w-full border-none"
|
||||
/>
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue