- {(chats.length > 0 || sharedChats.length > 0) && (
-
-
-
-
-
- {t("chats")} ({chats.length + sharedChats.length})
-
-
+ {/* Chat sections - fills available space */}
+ {isCollapsed ? (
+
+ {(chats.length > 0 || sharedChats.length > 0) && (
+
+
+
+
+
+ {t("chats")} ({chats.length + sharedChats.length})
+
+
+ )}
+
+ ) : (
+
+ {/* Shared Chats Section - takes half the space */}
+
+
+
+
+
+ {t("view_all_shared_chats") || "View all shared chats"}
+
+
+ ) : undefined
+ }
+ >
+ {sharedChats.length > 0 ? (
+
+
4 ? "pb-8" : ""}`}
+ >
+ {sharedChats.slice(0, 20).map((chat) => (
+ onChatSelect(chat)}
+ onArchive={() => onChatArchive?.(chat)}
+ onDelete={() => onChatDelete?.(chat)}
+ />
+ ))}
+
+ {/* Gradient fade indicator when more than 4 items */}
+ {sharedChats.length > 4 && (
+
+ )}
+
+ ) : (
+ {t("no_shared_chats")}
)}
-
- ) : (
-
- {/* Shared Chats Section */}
-
-
-
-
-
- {t("view_all_shared_chats") || "View all shared chats"}
-
-
- ) : undefined
- }
- >
- {sharedChats.length > 0 ? (
-
- {sharedChats.map((chat) => (
- onChatSelect(chat)}
- onArchive={() => onChatArchive?.(chat)}
- onDelete={() => onChatDelete?.(chat)}
- />
- ))}
-
- ) : (
- {t("no_shared_chats")}
- )}
-
+
- {/* Private Chats Section */}
-
-
-
-
-
- {t("view_all_private_chats") || "View all private chats"}
-
-
- ) : undefined
- }
- >
- {chats.length > 0 ? (
-
- {chats.map((chat) => (
+ {/* Private Chats Section - takes half the space */}
+
+
+
+
+
+ {t("view_all_private_chats") || "View all private chats"}
+
+
+ ) : undefined
+ }
+ >
+ {chats.length > 0 ? (
+
+
4 ? "pb-8" : ""}`}
+ >
+ {chats.slice(0, 20).map((chat) => (
))}
- ) : (
-
{t("no_chats")}
- )}
-
-
- )}
-
+ {/* Gradient fade indicator when more than 4 items */}
+ {chats.length > 4 && (
+
+ )}
+
+ ) : (
+ {t("no_chats")}
+ )}
+
+
+ )}
{/* Footer */}
diff --git a/surfsense_web/components/layout/ui/sidebar/SidebarSection.tsx b/surfsense_web/components/layout/ui/sidebar/SidebarSection.tsx
index 0ceafc113..bebb357ef 100644
--- a/surfsense_web/components/layout/ui/sidebar/SidebarSection.tsx
+++ b/surfsense_web/components/layout/ui/sidebar/SidebarSection.tsx
@@ -11,6 +11,8 @@ interface SidebarSectionProps {
children: React.ReactNode;
action?: React.ReactNode;
persistentAction?: React.ReactNode;
+ className?: string;
+ fillHeight?: boolean;
}
export function SidebarSection({
@@ -19,12 +21,18 @@ export function SidebarSection({
children,
action,
persistentAction,
+ className,
+ fillHeight = false,
}: SidebarSectionProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
return (
-
-
+
+
-
- {children}
+
+
+ {children}
+
);
diff --git a/surfsense_web/components/providers/ElectricProvider.tsx b/surfsense_web/components/providers/ElectricProvider.tsx
index 07d736c64..4aa83b304 100644
--- a/surfsense_web/components/providers/ElectricProvider.tsx
+++ b/surfsense_web/components/providers/ElectricProvider.tsx
@@ -1,7 +1,6 @@
"use client";
import { useAtomValue } from "jotai";
-import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
import { currentUserAtom } from "@/atoms/user/user-query.atoms";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
@@ -30,7 +29,6 @@ interface ElectricProviderProps {
* 5. Provides client via context - hooks should use useElectricClient()
*/
export function ElectricProvider({ children }: ElectricProviderProps) {
- const t = useTranslations("common");
const [electricClient, setElectricClient] = useState(null);
const [error, setError] = useState(null);
const {
@@ -117,7 +115,7 @@ export function ElectricProvider({ children }: ElectricProviderProps) {
const shouldShowLoading = hasToken && isUserLoaded && !!user?.id && !electricClient && !error;
// Use global loading hook with ownership tracking - prevents flash during transitions
- useGlobalLoadingEffect(shouldShowLoading, t("initializing"), "default");
+ useGlobalLoadingEffect(shouldShowLoading);
// For non-authenticated pages (like landing page), render immediately with null context
// Also render immediately if user query failed (e.g., token expired)
diff --git a/surfsense_web/components/providers/GlobalLoadingProvider.tsx b/surfsense_web/components/providers/GlobalLoadingProvider.tsx
index db66b9a64..08c888954 100644
--- a/surfsense_web/components/providers/GlobalLoadingProvider.tsx
+++ b/surfsense_web/components/providers/GlobalLoadingProvider.tsx
@@ -3,9 +3,7 @@
import { useAtomValue } from "jotai";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
-import { AmbientBackground } from "@/app/(home)/login/AmbientBackground";
import { globalLoadingAtom } from "@/atoms/ui/loading.atoms";
-import { Logo } from "@/components/Logo";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
@@ -18,7 +16,7 @@ import { cn } from "@/lib/utils";
*/
export function GlobalLoadingProvider({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false);
- const { isLoading, message, variant } = useAtomValue(globalLoadingAtom);
+ const { isLoading } = useAtomValue(globalLoadingAtom);
useEffect(() => {
setMounted(true);
@@ -36,35 +34,11 @@ export function GlobalLoadingProvider({ children }: { children: React.ReactNode
)}
aria-hidden={!isLoading}
>
- {variant === "login" ? (
-
-
-
-
-
-
- {/* Spinner is always mounted, animation never resets */}
-
-
-
- {message}
-
-
-
+
+
+
- ) : (
-
-
-
- {/* Spinner is always mounted, animation never resets */}
-
-
-
- {message}
-
-
-
- )}
+
);
diff --git a/surfsense_web/hooks/use-global-loading.ts b/surfsense_web/hooks/use-global-loading.ts
index baaa1f089..fee8ae18e 100644
--- a/surfsense_web/hooks/use-global-loading.ts
+++ b/surfsense_web/hooks/use-global-loading.ts
@@ -20,21 +20,18 @@ let pendingHideTimeout: ReturnType | null = null;
export function useGlobalLoading() {
const [loading, setLoading] = useAtom(globalLoadingAtom);
- const show = useCallback(
- (message?: string, variant: "login" | "default" = "default") => {
- // Cancel any pending hide - new loading request takes over
- if (pendingHideTimeout) {
- clearTimeout(pendingHideTimeout);
- pendingHideTimeout = null;
- }
+ const show = useCallback(() => {
+ // Cancel any pending hide - new loading request takes over
+ if (pendingHideTimeout) {
+ clearTimeout(pendingHideTimeout);
+ pendingHideTimeout = null;
+ }
- const id = ++loadingIdCounter;
- currentLoadingId = id;
- setLoading({ isLoading: true, message, variant });
- return id;
- },
- [setLoading]
- );
+ const id = ++loadingIdCounter;
+ currentLoadingId = id;
+ setLoading({ isLoading: true });
+ return id;
+ }, [setLoading]);
const hide = useCallback(
(id?: number) => {
@@ -50,7 +47,7 @@ export function useGlobalLoading() {
// Double-check we're still the current loading after the delay
if (id === undefined || id === currentLoadingId) {
currentLoadingId = null;
- setLoading({ isLoading: false, message: undefined, variant: "default" });
+ setLoading({ isLoading: false });
}
pendingHideTimeout = null;
}, 50); // Small delay to allow next component to mount and show loading
@@ -70,27 +67,21 @@ export function useGlobalLoading() {
* transition loading states (e.g., layout → page).
*
* @param shouldShow - Whether the loading screen should be visible
- * @param message - Optional message to display
- * @param variant - Visual style variant ("login" or "default")
*/
-export function useGlobalLoadingEffect(
- shouldShow: boolean,
- message?: string,
- variant: "login" | "default" = "default"
-) {
+export function useGlobalLoadingEffect(shouldShow: boolean) {
const { show, hide } = useGlobalLoading();
const loadingIdRef = useRef(null);
useEffect(() => {
if (shouldShow) {
// Show loading and store the ID
- loadingIdRef.current = show(message, variant);
+ loadingIdRef.current = show();
} else if (loadingIdRef.current !== null) {
// Only hide if we were the ones showing loading
hide(loadingIdRef.current);
loadingIdRef.current = null;
}
- }, [shouldShow, message, variant, show, hide]);
+ }, [shouldShow, show, hide]);
// Cleanup on unmount - only hide if we're still the active loading
useEffect(() => {
diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json
index 48f32466b..a9a75d8dc 100644
--- a/surfsense_web/messages/en.json
+++ b/surfsense_web/messages/en.json
@@ -2,8 +2,6 @@
"common": {
"app_name": "SurfSense",
"welcome": "Welcome",
- "loading": "Loading",
- "initializing": "Initializing",
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
@@ -80,8 +78,7 @@
"passwords_no_match_desc": "The passwords you entered do not match",
"creating_account": "Creating your account",
"creating_account_btn": "Creating account",
- "redirecting_login": "Redirecting to login page",
- "processing_authentication": "Processing authentication"
+ "redirecting_login": "Redirecting to login page"
},
"searchSpace": {
"create_title": "Create Search Space",
@@ -146,10 +143,7 @@
"api_keys": "API Keys",
"profile": "Profile",
"loading_dashboard": "Loading Dashboard",
- "checking_auth": "Checking authentication",
"loading_config": "Loading Configuration",
- "checking_llm_prefs": "Checking your LLM preferences",
- "setting_up_ai": "Setting up AI",
"config_error": "Configuration Error",
"failed_load_llm_config": "Failed to load your LLM configuration",
"error_loading_chats": "Error loading chats",
@@ -171,7 +165,6 @@
"create_search_space": "Create Search Space",
"add_new_search_space": "Add New Search Space",
"loading": "Loading",
- "fetching_spaces": "Fetching your search spaces",
"may_take_moment": "This may take a moment",
"error": "Error",
"something_wrong": "Something went wrong",
diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json
index 051327668..7c0fd8400 100644
--- a/surfsense_web/messages/zh.json
+++ b/surfsense_web/messages/zh.json
@@ -2,8 +2,6 @@
"common": {
"app_name": "SurfSense",
"welcome": "欢迎",
- "loading": "加载中...",
- "initializing": "正在初始化",
"save": "保存",
"cancel": "取消",
"delete": "删除",
@@ -80,8 +78,7 @@
"passwords_no_match_desc": "您输入的密码不一致",
"creating_account": "正在创建您的账户",
"creating_account_btn": "创建中",
- "redirecting_login": "正在跳转到登录页面",
- "processing_authentication": "正在处理身份验证"
+ "redirecting_login": "正在跳转到登录页面"
},
"searchSpace": {
"create_title": "创建搜索空间",
@@ -131,10 +128,7 @@
"api_keys": "API 密钥",
"profile": "个人资料",
"loading_dashboard": "正在加载仪表盘",
- "checking_auth": "正在检查身份验证",
"loading_config": "正在加载配置",
- "checking_llm_prefs": "正在检查您的 LLM 偏好设置",
- "setting_up_ai": "正在设置 AI",
"config_error": "配置错误",
"failed_load_llm_config": "无法加载您的 LLM 配置",
"error_loading_chats": "加载对话失败",
@@ -156,7 +150,6 @@
"create_search_space": "创建搜索空间",
"add_new_search_space": "添加新的搜索空间",
"loading": "加载中",
- "fetching_spaces": "正在获取您的搜索空间",
"may_take_moment": "这可能需要一些时间",
"error": "错误",
"something_wrong": "出现错误",