fix: remove message from loading UI

This commit is contained in:
Anish Sarkar 2026-01-27 15:28:30 +05:30
parent 2434e64093
commit ba304be977
13 changed files with 34 additions and 110 deletions

View file

@ -93,7 +93,7 @@ function LoginContent() {
}, [searchParams, t, tCommon]);
// Use global loading screen for auth type determination - spinner animation won't reset
useGlobalLoadingEffect(isLoading, tCommon("loading"), "login");
useGlobalLoadingEffect(isLoading);
// Show nothing while loading - the GlobalLoadingProvider handles the loading UI
if (isLoading) {

View file

@ -1,13 +1,10 @@
"use client";
import { useTranslations } from "next-intl";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
export default function AuthCallbackLoading() {
const t = useTranslations("auth");
// Use global loading - spinner animation won't reset when page transitions
useGlobalLoadingEffect(true, t("processing_authentication"), "default");
useGlobalLoadingEffect(true);
// Return null - the GlobalLoadingProvider handles the loading UI
return null;

View file

@ -154,11 +154,7 @@ export function DashboardClientLayout({
isAutoConfiguring;
// Use global loading screen - spinner animation won't reset
useGlobalLoadingEffect(
shouldShowLoading,
isAutoConfiguring ? t("setting_up_ai") : t("checking_llm_prefs"),
"default"
);
useGlobalLoadingEffect(shouldShowLoading);
if (shouldShowLoading) {
return null;

View file

@ -1,6 +1,5 @@
"use client";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
@ -10,11 +9,10 @@ interface DashboardLayoutProps {
}
export default function DashboardLayout({ children }: DashboardLayoutProps) {
const t = useTranslations("dashboard");
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
// Use the global loading screen - spinner animation won't reset
useGlobalLoadingEffect(isCheckingAuth, t("checking_auth"), "default");
useGlobalLoadingEffect(isCheckingAuth);
useEffect(() => {
// Check if user is authenticated

View file

@ -1,13 +1,10 @@
"use client";
import { useTranslations } from "next-intl";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
export default function DashboardLoading() {
const t = useTranslations("common");
// Use global loading - spinner animation won't reset when page transitions
useGlobalLoadingEffect(true, t("loading"), "default");
useGlobalLoadingEffect(true);
// Return null - the GlobalLoadingProvider handles the loading UI
return null;

View file

@ -106,7 +106,7 @@ export default function DashboardPage() {
const shouldShowLoading = isLoading || searchSpaces.length > 0;
// Use global loading screen - spinner animation won't reset
useGlobalLoadingEffect(shouldShowLoading, t("fetching_spaces"), "default");
useGlobalLoadingEffect(shouldShowLoading);
if (error) return <ErrorScreen message={error?.message || "Failed to load search spaces"} />;

View file

@ -2,29 +2,18 @@ import { atom } from "jotai";
interface GlobalLoadingState {
isLoading: boolean;
message?: string;
variant: "login" | "default";
}
export const globalLoadingAtom = atom<GlobalLoadingState>({
isLoading: false,
message: undefined,
variant: "default",
});
// Helper atom for showing global loading
export const showGlobalLoadingAtom = atom(
null,
(
get,
set,
{ message, variant = "default" }: { message?: string; variant?: "login" | "default" }
) => {
set(globalLoadingAtom, { isLoading: true, message, variant });
}
);
export const showGlobalLoadingAtom = atom(null, (get, set) => {
set(globalLoadingAtom, { isLoading: true });
});
// Helper atom for hiding global loading
export const hideGlobalLoadingAtom = atom(null, (get, set) => {
set(globalLoadingAtom, { isLoading: false, message: undefined, variant: "default" });
set(globalLoadingAtom, { isLoading: false });
});

View file

@ -1,7 +1,6 @@
"use client";
import { useSearchParams } from "next/navigation";
import { useTranslations } from "next-intl";
import { useEffect } from "react";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
import { getAndClearRedirectPath, setBearerToken } from "@/lib/auth-utils";
@ -27,11 +26,10 @@ const TokenHandler = ({
tokenParamName = "token",
storageKey = "surfsense_bearer_token",
}: TokenHandlerProps) => {
const t = useTranslations("auth");
const searchParams = useSearchParams();
// Always show loading for this component - spinner animation won't reset
useGlobalLoadingEffect(true, t("processing_authentication"), "default");
useGlobalLoadingEffect(true);
useEffect(() => {
// Only run on client-side

View file

@ -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<ElectricClient | null>(null);
const [error, setError] = useState<Error | null>(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)

View file

@ -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" ? (
<div className="relative w-full h-full overflow-hidden bg-background">
<AmbientBackground />
<div className="mx-auto flex h-screen max-w-lg flex-col items-center justify-center">
<Logo className="rounded-md" />
<div className="mt-8 flex flex-col items-center space-y-4">
<div className="h-12 w-12 flex items-center justify-center">
{/* Spinner is always mounted, animation never resets */}
<Spinner size="lg" className="text-muted-foreground" />
</div>
<span className="text-muted-foreground text-sm min-h-[1.25rem] text-center max-w-xs">
{message}
</span>
</div>
</div>
<div className="flex min-h-screen flex-col items-center justify-center bg-background">
<div className="h-12 w-12 flex items-center justify-center">
<Spinner size="lg" className="text-muted-foreground" />
</div>
) : (
<div className="flex min-h-screen flex-col items-center justify-center bg-background">
<div className="flex flex-col items-center space-y-4">
<div className="h-12 w-12 flex items-center justify-center">
{/* Spinner is always mounted, animation never resets */}
<Spinner size="xl" className="text-primary" />
</div>
<span className="text-muted-foreground text-sm min-h-[1.25rem] text-center max-w-md px-4">
{message}
</span>
</div>
</div>
)}
</div>
</div>
);

View file

@ -20,21 +20,18 @@ let pendingHideTimeout: ReturnType<typeof setTimeout> | 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<number | null>(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(() => {

View file

@ -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",

View file

@ -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": "出现错误",