merge: upstream/dev with migration renumbering

This commit is contained in:
CREDO23 2026-01-27 11:22:26 +02:00
commit a7145b2c63
176 changed files with 8791 additions and 3608 deletions

View file

@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import {
ArchiveIcon,
Loader2,
MessageCircleMore,
MoreHorizontal,
RotateCcwIcon,
@ -28,6 +27,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
@ -231,7 +231,7 @@ export function AllPrivateChatsSidebar({
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
className="fixed inset-y-0 left-0 z-70 w-80 bg-background shadow-xl flex flex-col pointer-events-auto isolate"
role="dialog"
aria-modal="true"
@ -304,7 +304,7 @@ export function AllPrivateChatsSidebar({
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : error ? (
<div className="text-center py-8 text-sm text-destructive">
@ -365,7 +365,7 @@ export function AllPrivateChatsSidebar({
disabled={isBusy}
>
{isDeleting ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Spinner size="xs" />
) : (
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
)}

View file

@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { format } from "date-fns";
import {
ArchiveIcon,
Loader2,
MessageCircleMore,
MoreHorizontal,
RotateCcwIcon,
@ -28,6 +27,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Spinner } from "@/components/ui/spinner";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { useDebouncedValue } from "@/hooks/use-debounced-value";
@ -231,7 +231,7 @@ export function AllSharedChatsSidebar({
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
className="fixed inset-y-0 left-0 z-70 w-80 bg-background shadow-xl flex flex-col pointer-events-auto isolate"
role="dialog"
aria-modal="true"
@ -304,7 +304,7 @@ export function AllSharedChatsSidebar({
<div className="flex-1 overflow-y-auto overflow-x-hidden p-2">
{isLoading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
<Spinner size="md" className="text-muted-foreground" />
</div>
) : error ? (
<div className="text-center py-8 text-sm text-destructive">
@ -365,7 +365,7 @@ export function AllSharedChatsSidebar({
disabled={isBusy}
>
{isDeleting ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<Spinner size="xs" />
) : (
<MoreHorizontal className="h-3.5 w-3.5 text-muted-foreground" />
)}

View file

@ -1,12 +1,13 @@
"use client";
import { MessageSquare, MoreHorizontal } from "lucide-react";
import { ArchiveIcon, MessageSquare, MoreHorizontal, RotateCcwIcon, Trash2 } from "lucide-react";
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { cn } from "@/lib/utils";
@ -14,11 +15,20 @@ import { cn } from "@/lib/utils";
interface ChatListItemProps {
name: string;
isActive?: boolean;
archived?: boolean;
onClick?: () => void;
onArchive?: () => void;
onDelete?: () => void;
}
export function ChatListItem({ name, isActive, onClick, onDelete }: ChatListItemProps) {
export function ChatListItem({
name,
isActive,
archived,
onClick,
onArchive,
onDelete,
}: ChatListItemProps) {
const t = useTranslations("sidebar");
return (
@ -48,15 +58,39 @@ export function ChatListItem({ name, isActive, onClick, onDelete }: ChatListItem
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" side="right">
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onDelete?.();
}}
className="text-destructive focus:text-destructive"
>
{t("delete")}
</DropdownMenuItem>
{onArchive && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onArchive();
}}
>
{archived ? (
<>
<RotateCcwIcon className="mr-2 h-4 w-4" />
<span>{t("unarchive") || "Restore"}</span>
</>
) : (
<>
<ArchiveIcon className="mr-2 h-4 w-4" />
<span>{t("archive") || "Archive"}</span>
</>
)}
</DropdownMenuItem>
)}
{onArchive && onDelete && <DropdownMenuSeparator />}
{onDelete && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
onDelete();
}}
className="text-destructive focus:text-destructive"
>
<Trash2 className="mr-2 h-4 w-4" />
<span>{t("delete")}</span>
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>

View file

@ -73,6 +73,17 @@ function getInitials(name: string | null | undefined, email: string | null | und
return "U";
}
/**
* Format count for display: shows numbers up to 999, then "1k+", "2k+", etc.
*/
function formatInboxCount(count: number): string {
if (count <= 999) {
return count.toString();
}
const thousands = Math.floor(count / 1000);
return `${thousands}k+`;
}
/**
* Get display name for connector type
*/
@ -82,6 +93,9 @@ function getConnectorTypeDisplayName(connectorType: string): string {
GOOGLE_CALENDAR_CONNECTOR: "Google Calendar",
GOOGLE_GMAIL_CONNECTOR: "Gmail",
GOOGLE_DRIVE_CONNECTOR: "Google Drive",
COMPOSIO_GOOGLE_DRIVE_CONNECTOR: "Composio Google Drive",
COMPOSIO_GMAIL_CONNECTOR: "Composio Gmail",
COMPOSIO_GOOGLE_CALENDAR_CONNECTOR: "Composio Google Calendar",
LINEAR_CONNECTOR: "Linear",
NOTION_CONNECTOR: "Notion",
SLACK_CONNECTOR: "Slack",
@ -482,7 +496,7 @@ export function InboxSidebar({
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ type: "spring", damping: 25, stiffness: 300 }}
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
className="fixed inset-y-0 left-0 z-70 w-90 bg-background shadow-xl flex flex-col pointer-events-auto isolate"
role="dialog"
aria-modal="true"
@ -765,7 +779,7 @@ export function InboxSidebar({
<AtSign className="h-4 w-4" />
<span>{t("mentions") || "Mentions"}</span>
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium">
{unreadMentionsCount}
{formatInboxCount(unreadMentionsCount)}
</span>
</span>
</TabsTrigger>
@ -777,7 +791,7 @@ export function InboxSidebar({
<History className="h-4 w-4" />
<span>{t("status") || "Status"}</span>
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-primary/20 text-muted-foreground text-xs font-medium">
{unreadStatusCount}
{formatInboxCount(unreadStatusCount)}
</span>
</span>
</TabsTrigger>

View file

@ -25,6 +25,7 @@ interface MobileSidebarProps {
onNewChat: () => void;
onChatSelect: (chat: ChatItem) => void;
onChatDelete?: (chat: ChatItem) => void;
onChatArchive?: (chat: ChatItem) => void;
onViewAllSharedChats?: () => void;
onViewAllPrivateChats?: () => void;
user: User;
@ -64,6 +65,7 @@ export function MobileSidebar({
onNewChat,
onChatSelect,
onChatDelete,
onChatArchive,
onViewAllSharedChats,
onViewAllPrivateChats,
user,
@ -141,6 +143,7 @@ export function MobileSidebar({
}}
onChatSelect={handleChatSelect}
onChatDelete={onChatDelete}
onChatArchive={onChatArchive}
onViewAllSharedChats={onViewAllSharedChats}
onViewAllPrivateChats={onViewAllPrivateChats}
user={user}

View file

@ -20,7 +20,9 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
const joyrideAttr =
item.title === "Documents" || item.title.toLowerCase().includes("documents")
? { "data-joyride": "documents-sidebar" }
: {};
: item.title === "Inbox" || item.title.toLowerCase().includes("inbox")
? { "data-joyride": "inbox-sidebar" }
: {};
if (isCollapsed) {
return (
@ -32,14 +34,13 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
className={cn(
"relative flex h-10 w-10 items-center justify-center rounded-md transition-colors",
"hover:bg-accent hover:text-accent-foreground",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
item.isActive && "bg-accent text-accent-foreground"
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
)}
{...joyrideAttr}
>
<Icon className="h-4 w-4" />
{item.badge && (
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center min-w-4 h-4 px-1 rounded-full bg-red-500 text-white text-[10px] font-medium">
<span className="absolute top-0.5 right-0.5 inline-flex items-center justify-center min-w-[14px] h-[14px] px-0.5 rounded-full bg-red-500 text-white text-[9px] font-medium">
{item.badge}
</span>
)}
@ -62,15 +63,14 @@ export function NavSection({ items, onItemClick, isCollapsed = false }: NavSecti
className={cn(
"flex items-center gap-2 rounded-md mx-2 px-2 py-1.5 text-sm transition-colors text-left",
"hover:bg-accent hover:text-accent-foreground",
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
item.isActive && "bg-accent text-accent-foreground"
"focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
)}
{...joyrideAttr}
>
<Icon className="h-4 w-4 shrink-0" />
<span className="flex-1 truncate">{item.title}</span>
{item.badge && (
<span className="inline-flex items-center justify-center min-w-5 h-5 px-1.5 rounded-full bg-red-500 text-white text-xs font-medium">
<span className="inline-flex items-center justify-center min-w-4 h-4 px-1 rounded-full bg-red-500 text-white text-[10px] font-medium">
{item.badge}
</span>
)}

View file

@ -1,6 +1,8 @@
"use client";
import { Mail } from "lucide-react";
import { Plus } from "lucide-react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { Progress } from "@/components/ui/progress";
interface PageUsageDisplayProps {
@ -9,6 +11,8 @@ interface PageUsageDisplayProps {
}
export function PageUsageDisplay({ pagesUsed, pagesLimit }: PageUsageDisplayProps) {
const params = useParams();
const searchSpaceId = params.search_space_id;
const usagePercentage = (pagesUsed / pagesLimit) * 100;
return (
@ -21,13 +25,13 @@ export function PageUsageDisplay({ pagesUsed, pagesLimit }: PageUsageDisplayProp
<span className="font-medium">{usagePercentage.toFixed(0)}%</span>
</div>
<Progress value={usagePercentage} className="h-1.5" />
<a
href="mailto:rohan@surfsense.com?subject=Request%20to%20Increase%20Page%20Limits"
<Link
href={`/dashboard/${searchSpaceId}/more-pages`}
className="flex items-center gap-1.5 text-[10px] text-muted-foreground hover:text-primary transition-colors"
>
<Mail className="h-3 w-3 shrink-0" />
<span>Contact to increase limits</span>
</a>
<Plus className="h-3 w-3 shrink-0" />
<span>Get More Pages</span>
</Link>
</div>
</div>
);

View file

@ -27,6 +27,7 @@ interface SidebarProps {
onNewChat: () => void;
onChatSelect: (chat: ChatItem) => void;
onChatDelete?: (chat: ChatItem) => void;
onChatArchive?: (chat: ChatItem) => void;
onViewAllSharedChats?: () => void;
onViewAllPrivateChats?: () => void;
user: User;
@ -52,6 +53,7 @@ export function Sidebar({
onNewChat,
onChatSelect,
onChatDelete,
onChatArchive,
onViewAllSharedChats,
onViewAllPrivateChats,
user,
@ -175,7 +177,9 @@ export function Sidebar({
key={chat.id}
name={chat.name}
isActive={chat.id === activeChatId}
archived={chat.archived}
onClick={() => onChatSelect(chat)}
onArchive={() => onChatArchive?.(chat)}
onDelete={() => onChatDelete?.(chat)}
/>
))}
@ -216,7 +220,9 @@ export function Sidebar({
key={chat.id}
name={chat.name}
isActive={chat.id === activeChatId}
archived={chat.archived}
onClick={() => onChatSelect(chat)}
onArchive={() => onChatArchive?.(chat)}
onDelete={() => onChatDelete?.(chat)}
/>
))}

View file

@ -1,6 +1,6 @@
"use client";
import { ChevronsUpDown, ScrollText, Settings, Users } from "lucide-react";
import { ChevronsUpDown, Logs, Settings, Users } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useTranslations } from "next-intl";
import { Button } from "@/components/ui/button";
@ -57,7 +57,7 @@ export function SidebarHeader({
{t("manage_members")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => router.push(`/dashboard/${searchSpaceId}/logs`)}>
<ScrollText className="mr-2 h-4 w-4" />
<Logs className="mr-2 h-4 w-4" />
{t("logs")}
</DropdownMenuItem>
<DropdownMenuSeparator />

View file

@ -1,6 +1,6 @@
"use client";
import { ChevronUp, Languages, Laptop, LogOut, Moon, Settings, Sun } from "lucide-react";
import { Check, ChevronUp, Languages, Laptop, LogOut, Moon, Settings, Sun } from "lucide-react";
import { useTranslations } from "next-intl";
import {
DropdownMenu,
@ -197,11 +197,12 @@ export function SidebarUserProfile({
className={cn(
"mb-1 last:mb-0 transition-all",
"hover:bg-accent/50",
isSelected && "bg-accent/80"
isSelected && "text-primary"
)}
>
<Icon className="mr-2 h-4 w-4" />
<span className="flex-1">{t(themeOption.value)}</span>
{isSelected && <Check className="h-4 w-4 shrink-0" />}
</DropdownMenuItem>
);
})}
@ -226,11 +227,12 @@ export function SidebarUserProfile({
className={cn(
"mb-1 last:mb-0 transition-all",
"hover:bg-accent/50",
isSelected && "bg-accent/80"
isSelected && "text-primary"
)}
>
<span className="mr-2">{language.flag}</span>
<span className="flex-1">{language.name}</span>
{isSelected && <Check className="h-4 w-4 shrink-0" />}
</DropdownMenuItem>
);
})}
@ -313,11 +315,12 @@ export function SidebarUserProfile({
className={cn(
"mb-1 last:mb-0 transition-all",
"hover:bg-accent/50",
isSelected && "bg-accent/80"
isSelected && "text-primary"
)}
>
<Icon className="mr-2 h-4 w-4" />
<span className="flex-1">{t(themeOption.value)}</span>
{isSelected && <Check className="h-4 w-4 shrink-0" />}
</DropdownMenuItem>
);
})}
@ -342,11 +345,12 @@ export function SidebarUserProfile({
className={cn(
"mb-1 last:mb-0 transition-all",
"hover:bg-accent/50",
isSelected && "bg-accent/80"
isSelected && "text-primary"
)}
>
<span className="mr-2">{language.flag}</span>
<span className="flex-1">{language.name}</span>
{isSelected && <Check className="h-4 w-4 shrink-0" />}
</DropdownMenuItem>
);
})}